Browse Source

Add sighash checks for signer

psbt
junderw 6 years ago
parent
commit
ccab2652f9
No known key found for this signature in database GPG Key ID: B256185D3A971908
  1. 65
      src/psbt.js
  2. 64
      ts_src/psbt.ts
  3. 8
      types/psbt.d.ts

65
src/psbt.js

@ -273,7 +273,7 @@ class Psbt extends bip174_1.Psbt {
} }
return results.every(res => res === true); return results.every(res => res === true);
} }
sign(keyPair) { sign(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input'); throw new Error('Need Signer to sign input');
// TODO: Add a pubkey/pubkeyhash cache to each input // TODO: Add a pubkey/pubkeyhash cache to each input
@ -282,7 +282,7 @@ class Psbt extends bip174_1.Psbt {
const results = []; const results = [];
for (const i of range(this.inputs.length)) { for (const i of range(this.inputs.length)) {
try { try {
this.signInput(i, keyPair); this.signInput(i, keyPair, sighashTypes);
results.push(true); results.push(true);
} catch (err) { } catch (err) {
results.push(false); results.push(false);
@ -293,7 +293,7 @@ class Psbt extends bip174_1.Psbt {
} }
return this; return this;
} }
signAsync(keyPair) { signAsync(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
return reject(new Error('Need Signer to sign input')); return reject(new Error('Need Signer to sign input'));
@ -304,7 +304,7 @@ class Psbt extends bip174_1.Psbt {
const promises = []; const promises = [];
for (const [i] of this.inputs.entries()) { for (const [i] of this.inputs.entries()) {
promises.push( promises.push(
this.signInputAsync(i, keyPair).then( this.signInputAsync(i, keyPair, sighashTypes).then(
() => { () => {
results.push(true); results.push(true);
}, },
@ -322,7 +322,11 @@ class Psbt extends bip174_1.Psbt {
}); });
}); });
} }
signInput(inputIndex, keyPair) { signInput(
inputIndex,
keyPair,
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
) {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input'); throw new Error('Need Signer to sign input');
const { hash, sighashType } = getHashAndSighashType( const { hash, sighashType } = getHashAndSighashType(
@ -330,6 +334,7 @@ class Psbt extends bip174_1.Psbt {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__CACHE, this.__CACHE,
sighashTypes,
); );
const partialSig = { const partialSig = {
pubkey: keyPair.publicKey, pubkey: keyPair.publicKey,
@ -337,7 +342,11 @@ class Psbt extends bip174_1.Psbt {
}; };
return this.addPartialSigToInput(inputIndex, partialSig); return this.addPartialSigToInput(inputIndex, partialSig);
} }
signInputAsync(inputIndex, keyPair) { signInputAsync(
inputIndex,
keyPair,
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
return reject(new Error('Need Signer to sign input')); return reject(new Error('Need Signer to sign input'));
@ -346,6 +355,7 @@ class Psbt extends bip174_1.Psbt {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__CACHE, this.__CACHE,
sighashTypes,
); );
Promise.resolve(keyPair.sign(hash)).then(signature => { Promise.resolve(keyPair.sign(hash)).then(signature => {
const partialSig = { const partialSig = {
@ -548,19 +558,37 @@ function getFinalScripts(
finalScriptWitness, finalScriptWitness,
}; };
} }
function getHashAndSighashType(inputs, inputIndex, pubkey, cache) { function getHashAndSighashType(
inputs,
inputIndex,
pubkey,
cache,
sighashTypes,
) {
const input = utils_1.checkForInput(inputs, inputIndex); const input = utils_1.checkForInput(inputs, inputIndex);
const { hash, sighashType, script } = getHashForSig(inputIndex, input, cache); const { hash, sighashType, script } = getHashForSig(
inputIndex,
input,
cache,
sighashTypes,
);
checkScriptForPubkey(pubkey, script, 'sign'); checkScriptForPubkey(pubkey, script, 'sign');
return { return {
hash, hash,
sighashType, sighashType,
}; };
} }
function getHashForSig(inputIndex, input, cache) { function getHashForSig(inputIndex, input, cache, sighashTypes) {
const unsignedTx = cache.__TX; const unsignedTx = cache.__TX;
const sighashType = const sighashType =
input.sighashType || transaction_1.Transaction.SIGHASH_ALL; input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
const str = sighashTypeToString(sighashType);
throw new Error(
`Sighash type is not allowed. Retry the sign method passing the ` +
`sighashTypes array of whitelisted types. Sighash type: ${str}`,
);
}
let hash; let hash;
let script; let script;
if (input.nonWitnessUtxo) { if (input.nonWitnessUtxo) {
@ -800,6 +828,25 @@ function scriptWitnessToWitnessStack(buffer) {
} }
return readVector(); return readVector();
} }
function sighashTypeToString(sighashType) {
let text =
sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY
? 'SIGHASH_ANYONECANPAY | '
: '';
const sigMod = sighashType & 0x1f;
switch (sigMod) {
case transaction_1.Transaction.SIGHASH_ALL:
text += 'SIGHASH_ALL';
break;
case transaction_1.Transaction.SIGHASH_SINGLE:
text += 'SIGHASH_SINGLE';
break;
case transaction_1.Transaction.SIGHASH_NONE:
text += 'SIGHASH_NONE';
break;
}
return text;
}
function witnessStackToScriptWitness(witness) { function witnessStackToScriptWitness(witness) {
let buffer = Buffer.allocUnsafe(0); let buffer = Buffer.allocUnsafe(0);
function writeSlice(slice) { function writeSlice(slice) {

64
ts_src/psbt.ts

@ -332,7 +332,10 @@ export class Psbt extends PsbtBase {
return results.every(res => res === true); return results.every(res => res === true);
} }
sign(keyPair: Signer): this { sign(
keyPair: Signer,
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
): this {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input'); throw new Error('Need Signer to sign input');
@ -342,7 +345,7 @@ export class Psbt extends PsbtBase {
const results: boolean[] = []; const results: boolean[] = [];
for (const i of range(this.inputs.length)) { for (const i of range(this.inputs.length)) {
try { try {
this.signInput(i, keyPair); this.signInput(i, keyPair, sighashTypes);
results.push(true); results.push(true);
} catch (err) { } catch (err) {
results.push(false); results.push(false);
@ -354,7 +357,10 @@ export class Psbt extends PsbtBase {
return this; return this;
} }
signAsync(keyPair: SignerAsync): Promise<void> { signAsync(
keyPair: SignerAsync,
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
): Promise<void> {
return new Promise( return new Promise(
(resolve, reject): any => { (resolve, reject): any => {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
@ -367,7 +373,7 @@ export class Psbt extends PsbtBase {
const promises: Array<Promise<void>> = []; const promises: Array<Promise<void>> = [];
for (const [i] of this.inputs.entries()) { for (const [i] of this.inputs.entries()) {
promises.push( promises.push(
this.signInputAsync(i, keyPair).then( this.signInputAsync(i, keyPair, sighashTypes).then(
() => { () => {
results.push(true); results.push(true);
}, },
@ -387,7 +393,11 @@ export class Psbt extends PsbtBase {
); );
} }
signInput(inputIndex: number, keyPair: Signer): this { signInput(
inputIndex: number,
keyPair: Signer,
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
): this {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input'); throw new Error('Need Signer to sign input');
const { hash, sighashType } = getHashAndSighashType( const { hash, sighashType } = getHashAndSighashType(
@ -395,6 +405,7 @@ export class Psbt extends PsbtBase {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__CACHE, this.__CACHE,
sighashTypes,
); );
const partialSig = { const partialSig = {
@ -405,7 +416,11 @@ export class Psbt extends PsbtBase {
return this.addPartialSigToInput(inputIndex, partialSig); return this.addPartialSigToInput(inputIndex, partialSig);
} }
signInputAsync(inputIndex: number, keyPair: SignerAsync): Promise<void> { signInputAsync(
inputIndex: number,
keyPair: SignerAsync,
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
): Promise<void> {
return new Promise( return new Promise(
(resolve, reject): void => { (resolve, reject): void => {
if (!keyPair || !keyPair.publicKey) if (!keyPair || !keyPair.publicKey)
@ -415,6 +430,7 @@ export class Psbt extends PsbtBase {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__CACHE, this.__CACHE,
sighashTypes,
); );
Promise.resolve(keyPair.sign(hash)).then(signature => { Promise.resolve(keyPair.sign(hash)).then(signature => {
@ -683,12 +699,18 @@ function getHashAndSighashType(
inputIndex: number, inputIndex: number,
pubkey: Buffer, pubkey: Buffer,
cache: PsbtCache, cache: PsbtCache,
sighashTypes: number[],
): { ): {
hash: Buffer; hash: Buffer;
sighashType: number; sighashType: number;
} { } {
const input = checkForInput(inputs, inputIndex); const input = checkForInput(inputs, inputIndex);
const { hash, sighashType, script } = getHashForSig(inputIndex, input, cache); const { hash, sighashType, script } = getHashForSig(
inputIndex,
input,
cache,
sighashTypes,
);
checkScriptForPubkey(pubkey, script, 'sign'); checkScriptForPubkey(pubkey, script, 'sign');
return { return {
hash, hash,
@ -700,6 +722,7 @@ function getHashForSig(
inputIndex: number, inputIndex: number,
input: PsbtInput, input: PsbtInput,
cache: PsbtCache, cache: PsbtCache,
sighashTypes?: number[],
): { ): {
script: Buffer; script: Buffer;
hash: Buffer; hash: Buffer;
@ -707,6 +730,13 @@ function getHashForSig(
} { } {
const unsignedTx = cache.__TX; const unsignedTx = cache.__TX;
const sighashType = input.sighashType || Transaction.SIGHASH_ALL; const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
const str = sighashTypeToString(sighashType);
throw new Error(
`Sighash type is not allowed. Retry the sign method passing the ` +
`sighashTypes array of whitelisted types. Sighash type: ${str}`,
);
}
let hash: Buffer; let hash: Buffer;
let script: Buffer; let script: Buffer;
@ -981,6 +1011,26 @@ function scriptWitnessToWitnessStack(buffer: Buffer): Buffer[] {
return readVector(); return readVector();
} }
function sighashTypeToString(sighashType: number): string {
let text =
sighashType & Transaction.SIGHASH_ANYONECANPAY
? 'SIGHASH_ANYONECANPAY | '
: '';
const sigMod = sighashType & 0x1f;
switch (sigMod) {
case Transaction.SIGHASH_ALL:
text += 'SIGHASH_ALL';
break;
case Transaction.SIGHASH_SINGLE:
text += 'SIGHASH_SINGLE';
break;
case Transaction.SIGHASH_NONE:
text += 'SIGHASH_NONE';
break;
}
return text;
}
function witnessStackToScriptWitness(witness: Buffer[]): Buffer { function witnessStackToScriptWitness(witness: Buffer[]): Buffer {
let buffer = Buffer.allocUnsafe(0); let buffer = Buffer.allocUnsafe(0);

8
types/psbt.d.ts

@ -27,10 +27,10 @@ export declare class Psbt extends PsbtBase {
finalizeInput(inputIndex: number): this; finalizeInput(inputIndex: number): this;
validateAllSignatures(): boolean; validateAllSignatures(): boolean;
validateSignatures(inputIndex: number, pubkey?: Buffer): boolean; validateSignatures(inputIndex: number, pubkey?: Buffer): boolean;
sign(keyPair: Signer): this; sign(keyPair: Signer, sighashTypes?: number[]): this;
signAsync(keyPair: SignerAsync): Promise<void>; signAsync(keyPair: SignerAsync, sighashTypes?: number[]): Promise<void>;
signInput(inputIndex: number, keyPair: Signer): this; signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this;
signInputAsync(inputIndex: number, keyPair: SignerAsync): Promise<void>; signInputAsync(inputIndex: number, keyPair: SignerAsync, sighashTypes?: number[]): Promise<void>;
} }
interface PsbtOptsOptional { interface PsbtOptsOptional {
network?: Network; network?: Network;

Loading…
Cancel
Save