libnexa-ts
Version:
A pure and powerful Nexa SDK library.
110 lines (93 loc) • 5.36 kB
text/typescript
import PublicKey from "../../../keys/publickey";
import ValidationUtils from "../../../utils/validation.utils";
import { Opcode } from "../../script/opcode";
import Script from "../../script/script";
import Input from "./input";
import type PrivateKey from "../../../keys/privatekey";
import type TxSignature from "../txsignature";
import ScriptFactory from "../../script/script.factory";
import Hash from "../../../crypto/hash";
import BufferUtils from "../../../utils/buffer.utils";
import type { IInput } from "../interfaces";
export default class ScriptTemplateInput extends Input {
public templateScript: Script;
public constraintScript?: Script;
public publicKey?: PublicKey;
/**
* Represents a special kind of input of generic ScriptTemplate kind.
*
* WARNING: this is a general case where the signature is similar to p2pkt and added to scriptSig as push signature data.
* If you have complex smart contract, consider extending this class (or Input class) and implement the necessary logic,
* or sign it manually.
*/
constructor(arg: IInput) {
ValidationUtils.validateArgument(typeof arg?.templateData === 'object' && arg?.templateData !== null, 'Missing template object');
ValidationUtils.validateArgument(typeof arg?.output === 'object' && arg?.output !== null, 'Missing associated utxo');
let td = arg.templateData!;
ValidationUtils.validateArgument(td.templateScript instanceof Script || typeof td.templateScript === 'string', 'Invalid template');
ValidationUtils.validateArgument(td.constraintScript instanceof Script || typeof td.constraintScript === 'string' || td.constraintScript == null, 'Invalid constraint');
ValidationUtils.validateArgument(td.publicKey instanceof PublicKey || typeof td.publicKey === 'string' || td.publicKey == null, 'Invalid pubkey');
super(arg);
this.templateScript = typeof td.templateScript === 'string' ? Script.fromString(td.templateScript) : td.templateScript;
this.constraintScript = typeof td.constraintScript === 'string' ? Script.fromString(td.constraintScript) : td.constraintScript;
this.publicKey = typeof td.publicKey === 'string' ? PublicKey.fromString(td.publicKey) : td.publicKey;
let templateHash = this.output!.scriptPubKey.getTemplateHash();
ValidationUtils.validateState(BufferUtils.isBuffer(templateHash) && BufferUtils.equals(templateHash, Hash.sha256ripemd160(this.templateScript.toBuffer())), "Provided template doesn't match to the provided output");
let constraintHash = this.output!.scriptPubKey.getConstraintHash();
if (BufferUtils.isBuffer(constraintHash)) {
ValidationUtils.validateState(this.constraintScript != null && !this.constraintScript.isEmpty(), "Provided constraint must not be undefined or empty if constraint hash is provided in output");
ValidationUtils.validateState(BufferUtils.equals(constraintHash, Hash.sha256ripemd160(this.constraintScript!.toBuffer())), "Provided constraint doesn't match to the provided output");
} else {
ValidationUtils.validateState(constraintHash === Opcode.OP_FALSE, "Provided output has invalid constraint hash, must be a hash or OP_FALSE");
ValidationUtils.validateState(this.constraintScript == null, "Provided constraint must be undefined if constraint hash is OP_FALSE");
}
}
public override toJSON = this.toObject;
public override toObject(): IInput {
let input = super.toObject()
return {
...input,
templateData: {
templateScript: this.templateScript.toHex(),
constraintScript: this.constraintScript?.toHex(),
publicKey: this.publicKey?.toString()
}
}
}
public static override fromObject(obj: IInput): ScriptTemplateInput {
ValidationUtils.validateArgument(typeof obj === 'object' && obj !== null, "obj");
return new ScriptTemplateInput(obj);
}
public override getSubscript(): Script {
return this.templateScript;
}
public override canSign(privateKey: PrivateKey): boolean {
return this.publicKey?.toString() === privateKey.publicKey.toString();
}
public override isFullySigned(): boolean {
return this.scriptSig.isScriptTemplateIn()
&& this.templateScript.equals(Script.fromBuffer(this.scriptSig.chunks[0].buf!))
&& (!(this.constraintScript instanceof Script) || this.constraintScript.equals(Script.fromBuffer(this.scriptSig.chunks[1].buf!)));
}
public override addSignature(signature: TxSignature): this {
let satisfier = Script.empty().add(signature.toTxSatisfier());
this.scriptSig = ScriptFactory.buildScriptTemplateIn(this.templateScript, this.constraintScript, satisfier);
return this;
}
public override estimateSize(): number {
if (this.isFullySigned()) {
return super.estimateSize();
}
let scriptSize = this._estimateScriptSize();
// type + outpoint + scriptlen + script + sequence + amount
return 1 + 32 + (scriptSize < 253 ? 1 : 3) + scriptSize + 4 + 8;
}
protected _estimateScriptSize(): number {
// here we calculate by template size, constraint size if not op_false, and satisfier size as sigsize (1 + 64)
let s = Script.empty().add(this.templateScript.toBuffer());
if (this.constraintScript instanceof Script) {
s.add(this.constraintScript.toBuffer());
}
return s.toBuffer().length + 1 + 64;
}
}