parkingsv-contract
Version:
ParkingSV library
269 lines (218 loc) • 6.58 kB
text/typescript
import bsv = require('bsv');
export { bsv };
const BN = bsv.crypto.BN;
const Interpreter = bsv.Script.Interpreter;
export const DEFAULT_FLAGS =
// @ts-ignore
Interpreter.SCRIPT_VERIFY_MINIMALDATA |
// @ts-ignore
Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID |
// @ts-ignore
Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES |
// @ts-ignore
Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES;
// @ts-ignore
export const DEFAULT_SIGHASH_TYPE =
// @ts-ignore
bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID;
export function bool2Asm(str: string): string {
if (str === 'true') {
return 'OP_TRUE';
}
if (str === 'false') {
return 'OP_FALSE';
}
throw new Error(`invalid str '${str}' to convert to bool`);
}
/**
* decimal int to little-endian signed magnitude
*/
export function int2Asm(str: string): string {
if (!/^-?(0x)?\d+$/.test(str)) {
throw new Error(`invalid str '${str}' to convert to int`);
}
// @ts-ignore
const number = new BN(str, 10);
// @ts-ignore
if (number.eqn(-1)) { return 'OP_1NEGATE'; }
// @ts-ignore
if (number.gten(0) && number.lten(16)) { return 'OP_' + str; }
// @ts-ignore
const m = number.toSM({ endian: 'little' });
return m.toString('hex');
}
/**
* convert literals to script ASM format
*/
export function literal2Asm(l: string): [string, string] {
// bool
if (l === 'false') {
return ['OP_FALSE', 'bool'];
}
if (l === 'true') {
return ['OP_TRUE', 'bool'];
}
// hex int
if (/^0x[0-9a-fA-F]+$/.test(l)) {
return [int2Asm(l), 'int'];
}
// decimal int
if (/^-?\d+$/.test(l)) {
return [int2Asm(l), 'int'];
}
// bytes
let m = /^b'([\da-fA-F]+)'$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'bytes'];
}
// PrivKey
// 1) decimal int
m = /^PrivKey\((-?\d+)\)$/.exec(l);
if (m) {
return [m[1], 'PrivKey'];
}
// 2) hex int
m = /^PrivKey\((0x[0-9a-fA-F]+)\)$/.exec(l);
if (m) {
return [m[1], 'PrivKey'];
}
// PubKey
m = /^PubKey\(b'([\da-fA-F]+)'\)$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'PubKey'];
}
// Sig
m = /^Sig\(b'([\da-fA-F]+)'\)$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'Sig'];
}
// Ripemd160
m = /^Ripemd160\(b'([\da-fA-F]+)'\)$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'Ripemd160'];
}
// Sha1
m = /^Sha1\(b'([\da-fA-F]+)'\)$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'Sha1'];
}
// Sha256
m = /^Sha256\(b'([\da-fA-F]+)'\)$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'Sha256'];
}
// SigHashType
m = /^SigHashType\(b'([\da-fA-F]+)'\)$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'SigHashType'];
}
// OpCodeType
m = /^OpCodeType\(b'([\da-fA-F]+)'\)$/.exec(l);
if (m) {
return [getValidatedHexString(m[1]), 'OpCodeType'];
}
throw new Error(`<${l}> can't be casted to asm format, only support sCrypt native types`);
}
export function bytes2Literal(bytearray: number[], type: string): string {
switch (type) {
case 'bool':
// @ts-ignore
return BN.fromBuffer(bytearray, { endian: 'little' }) > 0 ? 'true' : 'false';
case 'int':
// @ts-ignore
return BN.fromSM(bytearray, { endian: 'little' }).toString();
case 'bytes':
return `b'${bytesToHexString(bytearray)}'`;
default:
return `b'${bytesToHexString(bytearray)}'`;
}
}
export function bytesToHexString(bytearray: number[]): string {
return bytearray.reduce(function (o, c) { return o += ('0' + (c & 0xFF).toString(16)).slice(-2); }, '');
}
export function hexStringToBytes(hex: string): number[] {
getValidatedHexString(hex);
return hex.split('')
.reduce(function (o, c, i) {
if (i % 2 === 0) {
o.push(c);
} else {
o[o.length - 1] += c;
}
return o;
}, new Array<string>())
.map(b => parseInt(b, 16));
}
export function getValidatedHexString(hex: string, allowEmpty = true): string {
const ret = hex.trim();
if (ret.length < 1 && !allowEmpty) {
throw new Error("can't be empty string");
}
if (ret.length % 2) {
throw new Error('should have even length');
}
if (ret.length > 0 && !(/^[\da-f]+$/i.test(ret))) {
throw new Error('should only contain [0-9] or characters [a-fA-F]');
}
return ret;
}
export function signTx(tx, privateKey, lockingScriptASM: string, inputAmount: number, inputIndex = 0, sighashType = DEFAULT_SIGHASH_TYPE, flags = DEFAULT_FLAGS) {
if (!tx) {
throw new Error('param tx can not be empty');
}
if (!privateKey) {
throw new Error('param privateKey can not be empty');
}
if (!lockingScriptASM) {
throw new Error('param lockingScriptASM can not be empty');
}
if (!inputAmount) {
throw new Error('param inputAmount can not be empty');
}
// @ts-ignore
return bsv.Transaction.sighash.sign(
tx, privateKey, sighashType, inputIndex,
// @ts-ignore
bsv.Script.fromASM(lockingScriptASM), new bsv.crypto.BN(inputAmount), flags
).toTxFormat();
}
export function toHex(x: { toString(format: 'hex'): string }): string {
return x.toString('hex');
}
export function getPreimage(tx, inputLockingScriptASM: string, inputAmount: number, inputIndex = 0, sighashType = DEFAULT_SIGHASH_TYPE, flags = DEFAULT_FLAGS) {
// @ts-ignore
return bsv.Transaction.sighash.sighashPreimage(tx, sighashType, inputIndex, bsv.Script.fromASM(inputLockingScriptASM), new bsv.crypto.BN(inputAmount), flags);
}
// Converts a number into a sign-magnitude representation of certain size as a string
// Throws if the number cannot be accommodated
// Often used to append numbers to OP_RETURN, which are read in contracts
// TODO: handle bigint
export function num2bin(n: number, dataLen: number): string {
if (n === 0) {
return "00".repeat(dataLen);
}
// @ts-ignore
const num = BN.fromNumber(n);
const s = num.toSM({ endian: 'little' }).toString('hex');
const byteLen_ = s.length / 2;
if (byteLen_ > dataLen) {
throw new Error(`${n} cannot fit in ${dataLen} byte[s]`);
}
if (byteLen_ === dataLen) {
return s;
}
const paddingLen = dataLen - byteLen_;
const lastByte = s.substring(s.length - 2);
const rest = s.substring(0, s.length - 2);
let m = parseInt(lastByte, 16);
if (n < 0) {
// reset sign bit
m &= 0x7F;
}
let mHex = m.toString(16);
if (mHex.length < 2) {
mHex = '0' + mHex;
}
const padding = n > 0 ? '00'.repeat(paddingLen) : '00'.repeat(paddingLen - 1) + '80';
return rest + mHex + padding;
}