libnexa-ts
Version:
A pure and powerful Nexa SDK library.
231 lines (205 loc) • 9.53 kB
text/typescript
import Schnorr from "../../crypto/schnorr";
import type Signature from "../../crypto/signature";
import type PrivateKey from "../../keys/privatekey";
import ValidationUtils from "../../utils/validation.utils";
import type Script from "../script/script";
import type SighashType from "./sighashtype";
import { InputSighashType, OutputSighashType } from "./sighashtype";
import type Transaction from "./transaction";
import type PublicKey from "../../keys/publickey";
import BufferWriter from "../../encoding/bufferwriter";
import Hash from "../../crypto/hash";
import BN from "../../crypto/bn.extension";
import TxSignature from "./txsignature";
interface SighashComponents {
hashPrevouts: Uint8Array;
hashSequence: Uint8Array;
hashInputAmounts: Uint8Array;
hashOutputs: Uint8Array;
}
export default class TxSigner {
/**
* Build a transaction signature object (`TxSignature`) for a given input.
*
* This method extracts the subscript for the specified input, signs the
* transaction using the provided private key and sighash type, and returns
* a `TxSignature` containing all the components needed for inclusion in a scriptSig.
*
* @param transaction The transaction to sign.
* @param inputNumber The zero-based index of the input within the transaction.
* @param sighashType The sighash type that determines which parts of the transaction are signed.
* @param privateKey The private key used to generate the signature.
*
* @returns A `TxSignature` object.
*/
public static buildTxSignature(transaction: Transaction, inputNumber: number, sighashType: SighashType, privateKey: PrivateKey): TxSignature {
let subscript = transaction.inputs[inputNumber].getSubscript();
return new TxSignature({
inputIndex: inputNumber,
publicKey: privateKey.publicKey,
subscript: subscript,
signature: this.sign(transaction, inputNumber, sighashType, subscript, privateKey),
sigType: sighashType
});
}
/**
* Create a signature
*
* @param transaction the transaction to sign
* @param inputNumber the input index for the signature
* @param sighashType the sighash type
* @param subscript the script that will be signed
* @param privateKey the privkey to sign with
* @returns The signature
*/
public static sign(transaction: Transaction, inputNumber: number, sighashType: SighashType, subscript: Script, privateKey: PrivateKey): Signature {
let hashbuf = this.buildSighash(transaction, inputNumber, sighashType, subscript);
return Schnorr.sign(hashbuf, privateKey, 'little') as Signature;
}
/**
* Verify a signature
*
* @param transaction the transaction to verify
* @param inputNumber the input index for the signature
* @param signature the signature to verify
* @param sighashType the sighash type
* @param subscript the script that will be verified
* @param publicKey the pubkey that correspond to the signing privkey
* @returns true if signature is valid
*/
public static verify(transaction: Transaction, inputNumber: number, signature: Signature, sighashType: SighashType, subscript: Script, publicKey: PublicKey): boolean {
ValidationUtils.validateArgument(transaction != null, 'transaction');
ValidationUtils.validateArgument(signature != null, 'signature');
ValidationUtils.validateArgument(sighashType != null, 'sighashType');
let hashbuf = this.buildSighash(transaction, inputNumber, sighashType, subscript);
return Schnorr.verify(hashbuf, signature, publicKey, 'little');
}
/**
* Returns a buffer of length 32 bytes with the hash that needs to be signed for OP_CHECKSIG(VERIFY).
*
* @param transaction the transaction to sign
* @param inputNumber the input index for the signature
* @param sighashType the sighash type
* @param subscript the script that will be signed
*/
public static buildSighash(transaction: Transaction, inputNumber: number, sighashType: SighashType, subscript: Script): Uint8Array {
let components = this._getSighashComponents(transaction, inputNumber, sighashType);
let writer = new BufferWriter();
// Version
writer.writeUInt8(transaction.version);
// Input prevouts/nSequence (none/all, depending on flags)
writer.write(components.hashPrevouts);
writer.write(components.hashInputAmounts);
writer.write(components.hashSequence);
// scriptCode of the input (serialized as scripts inside CTxOuts)
writer.writeVarLengthBuf(subscript.toBuffer());
// Outputs (none/one/all, depending on flags)
writer.write(components.hashOutputs);
// Locktime
writer.writeUInt32LE(transaction.nLockTime);
// sighashType
writer.writeVarLengthBuf(sighashType.toBuffer());
let buf = writer.toBuffer();
return Hash.sha256sha256(buf).reverse();
}
private static _getSighashComponents(transaction: Transaction, inputNumber: number, sighashType: SighashType): SighashComponents {
ValidationUtils.validateArgument(!sighashType.isInvalid(), 'sighashType');
let hashPrevouts: Uint8Array, hashSequence: Uint8Array, hashInputAmounts: Uint8Array, hashOutputs: Uint8Array;
switch (sighashType.inType) {
case InputSighashType.FIRSTN:
let firstN = sighashType.inData[0];
ValidationUtils.validateArgument(firstN <= transaction.inputs.length, 'firstN out of range');
hashPrevouts = this._getPrevoutHash(transaction, firstN);
hashSequence = this._getSequenceHash(transaction, firstN);
hashInputAmounts = this._getInputAmountHash(transaction, firstN);
break;
case InputSighashType.THISIN:
ValidationUtils.validateArgument(inputNumber < transaction.inputs.length, 'inputNumber out of range');
hashPrevouts = this._getPrevoutHashOf(transaction, inputNumber);
hashSequence = this._getSequenceHashOf(transaction, inputNumber);
hashInputAmounts = this._getInputAmountHashOf(transaction, inputNumber);
break;
default: // ALL
hashPrevouts = this._getPrevoutHash(transaction, transaction.inputs.length);
hashSequence = this._getSequenceHash(transaction, transaction.inputs.length);
hashInputAmounts = this._getInputAmountHash(transaction, transaction.inputs.length);
break;
}
switch (sighashType.outType) {
case OutputSighashType.FIRSTN:
let firstN = sighashType.outData[0];
ValidationUtils.validateArgument(firstN <= transaction.outputs.length, 'firstN out of range');
hashOutputs = this._getOutputsHash(transaction, firstN);
break;
case OutputSighashType.TWO:
let [out1, out2] = sighashType.outData;
ValidationUtils.validateArgument(out1 < transaction.outputs.length, 'out1 out of range');
ValidationUtils.validateArgument(out2 < transaction.outputs.length, 'out2 out of range');
hashOutputs = this._getOutputsHashOf(transaction, out1, out2);
break;
default: // ALL
hashOutputs = this._getOutputsHash(transaction, transaction.outputs.length);
break;
}
return { hashPrevouts, hashSequence, hashInputAmounts, hashOutputs };
}
private static _getPrevoutHash(tx: Transaction, firstN: number): Uint8Array {
let writer = new BufferWriter();
for (let i = 0; i < firstN; i++) {
writer.writeUInt8(tx.inputs[i].type);
writer.writeReverse(tx.inputs[i].outpoint);
}
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
private static _getPrevoutHashOf(tx: Transaction, inputNumber: number): Uint8Array {
let writer = new BufferWriter();
writer.writeUInt8(tx.inputs[inputNumber].type);
writer.writeReverse(tx.inputs[inputNumber].outpoint);
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
private static _getSequenceHash(tx: Transaction, firstN: number): Uint8Array {
let writer = new BufferWriter();
for (let i = 0; i < firstN; i++) {
writer.writeUInt32LE(tx.inputs[i].sequenceNumber);
}
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
private static _getSequenceHashOf(tx: Transaction, inputNumber: number): Uint8Array {
let writer = new BufferWriter();
writer.writeUInt32LE(tx.inputs[inputNumber].sequenceNumber);
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
private static _getInputAmountHash(tx: Transaction, firstN: number): Uint8Array {
let writer = new BufferWriter();
for (let i = 0; i < firstN; i++) {
writer.writeUInt64LEBN(BN.fromBigInt(tx.inputs[i].amount));
}
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
private static _getInputAmountHashOf(tx: Transaction, inputNumber: number): Uint8Array {
let writer = new BufferWriter();
writer.writeUInt64LEBN(BN.fromBigInt(tx.inputs[inputNumber].amount));
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
private static _getOutputsHash(tx: Transaction, firstN: number): Uint8Array {
let writer = new BufferWriter();
for (let i = 0; i < firstN; i++) {
tx.outputs[i].toBufferWriter(writer);
}
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
private static _getOutputsHashOf(tx: Transaction, out1: number, out2: number): Uint8Array {
let writer = new BufferWriter();
tx.outputs[out1].toBufferWriter(writer);
tx.outputs[out2].toBufferWriter(writer);
let buf = writer.toBuffer();
return Hash.sha256sha256(buf);
}
}