Add sighash checks for signer
This commit is contained in:
parent
fa897cf78e
commit
ccab2652f9
3 changed files with 117 additions and 20 deletions
65
src/psbt.js
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) {
|
||||||
|
|
|
@ -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
vendored
8
types/psbt.d.ts
vendored
|
@ -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…
Add table
Add a link
Reference in a new issue