From 6b7a4984f8b6cb27a81cc66ca02314800caf5713 Mon Sep 17 00:00:00 2001 From: hh Date: Wed, 3 Apr 2024 10:53:00 +0800 Subject: [PATCH 1/3] use ts-sdk --- src/abi.ts | 153 ++++++++++++++--------------- src/builtins.ts | 33 +++---- src/compilerWrapper.ts | 2 +- src/contract.ts | 212 ++++++++++++++++++++++++----------------- src/deserializer.ts | 19 ++-- src/index.ts | 4 +- src/launchConfig.ts | 2 +- src/serializer.ts | 16 ++-- src/stateful.ts | 46 ++++----- src/utils.ts | 144 ++++++++++++++-------------- 10 files changed, 342 insertions(+), 289 deletions(-) diff --git a/src/abi.ts b/src/abi.ts index 4d6f698e..f9dfbc6d 100644 --- a/src/abi.ts +++ b/src/abi.ts @@ -1,3 +1,4 @@ +import { LockingScript, OP, Script, UnlockingScript, Utils } from '@bsv/sdk'; import { bin2num } from './builtins'; import { ABIEntity, ABIEntityType } from './compilerWrapper'; import { AbstractContract, AsmVarValues, TxContext, VerifyResult } from './contract'; @@ -7,9 +8,8 @@ import { SupportedParamType, TypeResolver, Int } from './scryptTypes'; import { toScriptHex } from './serializer'; import Stateful from './stateful'; import { flatternArg } from './typeCheck'; -import { asm2int, bsv, buildContractCode, int2Asm } from './utils'; - -export type Script = bsv.Script; +import { asm2int, buildContractCode, int2Asm } from './utils'; +import ScriptChunk from '@bsv/sdk/dist/types/src/script/ScriptChunk'; export type FileUri = string; @@ -50,19 +50,19 @@ export class FunctionCall { readonly args: Arguments = []; - private _unlockingScript?: Script; + private _unlockingScript?: UnlockingScript; - private _lockingScript?: Script; + private _lockingScript?: LockingScript; - get unlockingScript(): Script | undefined { + get unlockingScript(): UnlockingScript | undefined { return this._unlockingScript; } - get lockingScript(): Script | undefined { + get lockingScript(): LockingScript | undefined { return this._lockingScript; } - set lockingScript(s: Script | undefined) { + set lockingScript(s: LockingScript | undefined) { this._lockingScript = s; } @@ -70,8 +70,8 @@ export class FunctionCall { public methodName: string, binding: { contract: AbstractContract; - unlockingScript?: Script; - lockingScript?: Script; + unlockingScript?: UnlockingScript; + lockingScript?: LockingScript; args: Arguments; } ) { @@ -105,7 +105,7 @@ export class FunctionCall { if (this.lockingScript) { return this.lockingScript; } else { - return this.unlockingScript as Script; + return this.unlockingScript; } } @@ -115,7 +115,7 @@ export class FunctionCall { - genLaunchConfig(txContext?: TxContext): FileUri { + genLaunchConfig(): FileUri { const pubFunc: string = this.methodName; const name = `Debug ${this.contract.contractName}`; @@ -130,17 +130,17 @@ export class FunctionCall { Object.assign(state, { opReturn: this.contract.dataPart.toASM() }); } - const txCtx: TxContext = Object.assign({}, this.contract.txContext || {}, txContext || {}, state) as TxContext; + const txCtx: TxContext = Object.assign({}, this.contract.txContext || {}, state) as TxContext; return genLaunchConfigFile(this.contract.resolver, this.contract.ctorArgs(), this.args, pubFunc, name, program, txCtx, asmArgs); } - verify(txContext?: TxContext): VerifyResult { - const result = this.contract.run_verify(this.unlockingScript, txContext); + verify(): VerifyResult { + const result = this.contract.run_verify(this.unlockingScript); if (!result.success) { - const debugUrl = this.genLaunchConfig(txContext); + const debugUrl = this.genLaunchConfig(); if (debugUrl) { result.error = result.error + `\t[Launch Debugger](${debugUrl.replace(/file:/i, 'scryptlaunch:')})\n`; } @@ -157,7 +157,7 @@ export interface CallData { /** name of public function */ methodName: string; /** unlocking Script */ - unlockingScript: bsv.Script; + unlockingScript: UnlockingScript; /** function arguments */ args: Arguments; } @@ -221,7 +221,7 @@ export class ABICoder { } encodeConstructorCallFromRawHex(contract: AbstractContract, hexTemplate: string, raw: string): FunctionCall { - const script = bsv.Script.fromHex(raw); + const script = LockingScript.fromHex(raw); const constructorABI = this.abi.filter(entity => entity.type === ABIEntityType.CONSTRUCTOR)[0]; const cParams = constructorABI?.params || []; @@ -232,103 +232,103 @@ export class ABICoder { let codePartEndIndex = -1; const err = new Error(`the raw script cannot match the ASM template of contract ${contract.contractName}`); - function checkOp(chunk: bsv.Script.IOpChunk) { + function checkOp(chunk: ScriptChunk) { const op = hexTemplate.substring(offset, offset + 2); - if (parseInt(op, 16) != chunk.opcodenum) { + if (parseInt(op, 16) != chunk.op) { throw err; } offset = offset + 2; } - function checkPushByteLength(chunk: bsv.Script.IOpChunk) { + function checkPushByteLength(chunk: ScriptChunk) { const op = hexTemplate.substring(offset, offset + 2); - if (parseInt(op, 16) != chunk.opcodenum) { + if (parseInt(op, 16) != chunk.op) { throw err; } offset = offset + 2; - const data = hexTemplate.substring(offset, offset + chunk.len * 2); + const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (chunk.buf.toString('hex') != data) { + if (Utils.toHex(chunk.data) != data) { throw err; } - offset = offset + chunk.len * 2; + offset = offset + chunk.data.length * 2; } - function checkPushData1(chunk: bsv.Script.IOpChunk) { + function checkPushData1(chunk: ScriptChunk) { const op = hexTemplate.substring(offset, offset + 2); - if (parseInt(op, 16) != chunk.opcodenum) { + if (parseInt(op, 16) != chunk.op) { throw err; } offset = offset + 2; const next1Byte = hexTemplate.substring(offset, offset + 2); - if (parseInt(next1Byte, 16) != chunk.len) { + if (parseInt(next1Byte, 16) != chunk.data.length) { throw err; } offset = offset + 2; - const data = hexTemplate.substring(offset, offset + chunk.len * 2); + const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (chunk.buf.toString('hex') != data) { + if (Utils.toHex(chunk.data) != data) { throw err; } - offset = offset + chunk.len * 2; + offset = offset + chunk.data.length * 2; } - function checkPushData2(chunk: bsv.Script.IOpChunk) { + function checkPushData2(chunk: ScriptChunk) { const op = hexTemplate.substring(offset, offset + 2); - if (parseInt(op, 16) != chunk.opcodenum) { + if (parseInt(op, 16) != chunk.op) { throw err; } offset = offset + 2; const next2Byte = hexTemplate.substring(offset, offset + 4); - if (bin2num(next2Byte) != BigInt(chunk.len)) { + if (bin2num(next2Byte) != BigInt(chunk.data.length)) { throw err; } offset = offset + 4; - const data = hexTemplate.substring(offset, offset + chunk.len * 2); + const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (chunk.buf.toString('hex') != data) { + if (Utils.toHex(chunk.data) != data) { throw err; } - offset = offset + chunk.len * 2; + offset = offset + chunk.data.length * 2; } - function checkPushData4(chunk: bsv.Script.IOpChunk) { + function checkPushData4(chunk: ScriptChunk) { const op = hexTemplate.substring(offset, offset + 2); - if (parseInt(op, 16) != chunk.opcodenum) { + if (parseInt(op, 16) != chunk.op) { throw err; } offset = offset + 2; const next4Byte = hexTemplate.substring(offset, offset + 8); - if (bin2num(next4Byte) != BigInt(chunk.len)) { + if (bin2num(next4Byte) != BigInt(chunk.data.length)) { throw err; } offset = offset + 8; - const data = hexTemplate.substring(offset, offset + chunk.len * 2); + const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (chunk.buf.toString('hex') != data) { + if (Utils.toHex(chunk.data) != data) { throw err; } - offset = offset + chunk.len * 2; + offset = offset + chunk.data.length * 2; } function findTemplateVariable() { @@ -353,44 +353,47 @@ export class ABICoder { } } - function saveTemplateVariableValue(name: string, chunk: bsv.Script.IOpChunk) { - const bw = new bsv.encoding.BufferWriter(); - - bw.writeUInt8(chunk.opcodenum); - if (chunk.buf) { - if (chunk.opcodenum < bsv.Opcode.OP_PUSHDATA1) { - bw.write(chunk.buf); - } else if (chunk.opcodenum === bsv.Opcode.OP_PUSHDATA1) { - bw.writeUInt8(chunk.len); - bw.write(chunk.buf); - } else if (chunk.opcodenum === bsv.Opcode.OP_PUSHDATA2) { - bw.writeUInt16LE(chunk.len); - bw.write(chunk.buf); - } else if (chunk.opcodenum === bsv.Opcode.OP_PUSHDATA4) { - bw.writeUInt32LE(chunk.len); - bw.write(chunk.buf); + function saveTemplateVariableValue(name: string, chunk: ScriptChunk) { + const bw = new Utils.Writer(); + + bw.writeUInt8(chunk.op); + if (chunk.op > 0) { + if (chunk.op < OP.OP_PUSHDATA1) { + bw.write(chunk.data); + } else if (chunk.op === OP.OP_PUSHDATA1) { + bw.writeUInt8(chunk.data.length); + bw.write(chunk.data); + } else if (chunk.op === OP.OP_PUSHDATA2) { + bw.writeUInt16LE(chunk.data.length); + bw.write(chunk.data); + } else if (chunk.op === OP.OP_PUSHDATA4) { + bw.writeUInt32LE(chunk.data.length); + bw.write(chunk.data); } } + + if (name.startsWith(`<${contract.contractName}.`)) { //inline asm - contract.hexTemplateInlineASM.set(name, bw.toBuffer().toString('hex')); + contract.hexTemplateInlineASM.set(name, Utils.toHex(bw.toArray())); } else { - contract.hexTemplateArgs.set(name, bw.toBuffer().toString('hex')); + contract.hexTemplateArgs.set(name, Utils.toHex(bw.toArray())); } } + for (let index = 0; index < script.chunks.length; index++) { const chunk = script.chunks[index]; let breakfor = false; switch (true) { - case (chunk.opcodenum === 106): + case (chunk.op === 106): { if (offset >= hexTemplate.length) { - const b = bsv.Script.fromChunks(script.chunks.slice(index + 1)); + const b = new Script(script.chunks.slice(index + 1)); dataPartInHex = b.toHex(); codePartEndIndex = index; @@ -401,7 +404,7 @@ export class ABICoder { break; } - case (chunk.opcodenum === 0): { + case (chunk.op === 0): { const variable = findTemplateVariable(); if (variable) { @@ -413,7 +416,7 @@ export class ABICoder { break; } - case (chunk.opcodenum >= 1 && chunk.opcodenum <= 75): + case (chunk.op >= 1 && chunk.op <= 75): { const variable = findTemplateVariable(); @@ -425,7 +428,7 @@ export class ABICoder { break; } - case (chunk.opcodenum >= 79 && chunk.opcodenum <= 96): + case (chunk.op >= 79 && chunk.op <= 96): { const variable = findTemplateVariable(); @@ -437,7 +440,7 @@ export class ABICoder { break; } - case (chunk.opcodenum === 76): + case (chunk.op === 76): { const variable = findTemplateVariable(); @@ -448,7 +451,7 @@ export class ABICoder { } break; } - case (chunk.opcodenum === 77): + case (chunk.op === 77): { const variable = findTemplateVariable(); @@ -459,7 +462,7 @@ export class ABICoder { } break; } - case (chunk.opcodenum === 78): + case (chunk.op === 78): { const variable = findTemplateVariable(); @@ -511,7 +514,7 @@ export class ABICoder { return new FunctionCall('constructor', { contract, - lockingScript: codePartEndIndex > -1 ? bsv.Script.fromChunks(script.chunks.slice(0, codePartEndIndex)) : script, + lockingScript: codePartEndIndex > -1 ? new Script(script.chunks.slice(0, codePartEndIndex)) : script, args: ctorArgs }); @@ -535,10 +538,10 @@ export class ABICoder { if (this.abi.length > 2 && entity.index !== undefined) { // selector when there are multiple public functions const pubFuncIndex = entity.index; - hex += `${bsv.Script.fromASM(int2Asm(pubFuncIndex.toString())).toHex()}`; + hex += `${UnlockingScript.fromASM(int2Asm(pubFuncIndex.toString())).toHex()}`; } return new FunctionCall(name, { - contract, unlockingScript: bsv.Script.fromHex(hex), args: entity.params.map((param, index) => ({ + contract, unlockingScript: UnlockingScript.fromHex(hex), args: entity.params.map((param, index) => ({ name: param.name, type: param.type, value: args_[index] @@ -571,9 +574,9 @@ export class ABICoder { */ parseCallData(hex: string): CallData { - const unlockingScript = bsv.Script.fromHex(hex); + const unlockingScript = UnlockingScript.fromHex(hex); - const usASM = unlockingScript.toASM() as string; + const usASM = unlockingScript.toASM(); const pubFunAbis = this.abi.filter(entity => entity.type === 'function'); const pubFunCount = pubFunAbis.length; @@ -617,7 +620,7 @@ export class ABICoder { dummyArgs.forEach((farg: Argument, index: number) => { - hexTemplateArgs.set(`<${farg.name}>`, bsv.Script.fromASM(asmOpcodes[index]).toHex()); + hexTemplateArgs.set(`<${farg.name}>`, UnlockingScript.fromASM(asmOpcodes[index]).toHex()); }); diff --git a/src/builtins.ts b/src/builtins.ts index e3e870a7..8385648b 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -1,12 +1,13 @@ -import { bsv, Bytes, Int, Ripemd160 } from '.'; +import { BigNumber, Hash, Script, Utils } from '@bsv/sdk'; +import { Bytes, Int, Ripemd160 } from '.'; /** * bigint can be converted to string with pack * @category Bytes Operations */ export function pack(n: bigint): Bytes { - const num = new bsv.crypto.BN(n); - return num.toSM({ endian: 'little' }).toString('hex'); + const num = new BigNumber(n.toString().replace(/n/, '')); + return Utils.toHex(num.toSm('little')) } /** @@ -24,11 +25,11 @@ export function unpack(a: Bytes): bigint { // Often used to append numbers to OP_RETURN, which are read in contracts // Support Bigint export function num2bin(n: bigint, dataLen: number): string { - const num = new bsv.crypto.BN(n); + const num = new BigNumber(n.toString().replace(/n/, '')); if (num.eqn(0)) { return '00'.repeat(dataLen); } - const s = num.toSM({ endian: 'little' }).toString('hex'); + const s = Utils.toHex(num.toSm('little')) const byteLen_ = s.length / 2; if (byteLen_ > dataLen) { @@ -66,7 +67,7 @@ export function bin2num(hex: string): bigint { nHex = '0' + nHex; } //Support negative number - let bn = bsv.crypto.BN.fromHex(rest + nHex, { endian: 'little' }); + let bn = BigNumber.fromHex(rest + nHex, 'little'); if (m >> 7) { bn = bn.neg(); } @@ -176,13 +177,13 @@ export function writeVarint(b: string): string { } -export function buildOpreturnScript(data: string): bsv.Script { - return bsv.Script.fromASM(['OP_FALSE', 'OP_RETURN', data].join(' ')); +export function buildOpreturnScript(data: string): Script { + return Script.fromASM(['OP_FALSE', 'OP_RETURN', data].join(' ')); } -export function buildPublicKeyHashScript(pubKeyHash: Ripemd160): bsv.Script { - return bsv.Script.fromASM(['OP_DUP', 'OP_HASH160', pubKeyHash, 'OP_EQUALVERIFY', 'OP_CHECKSIG'].join(' ')); +export function buildPublicKeyHashScript(pubKeyHash: Ripemd160): Script { + return Script.fromASM(['OP_DUP', 'OP_HASH160', pubKeyHash, 'OP_EQUALVERIFY', 'OP_CHECKSIG'].join(' ')); } @@ -190,17 +191,17 @@ export function buildPublicKeyHashScript(pubKeyHash: Ripemd160): bsv.Script { // Equivalent to the built-in function `hash160` in scrypt -export function hash160(hexstr: string, encoding?: BufferEncoding): string { - return bsv.crypto.Hash.sha256ripemd160(Buffer.from(hexstr, encoding || 'hex')).toString('hex'); +export function hash160(hexstr: string, encoding?: 'hex' | 'utf8'): string { + return Utils.toHex(Hash.hash160(hexstr, encoding || 'hex')); } // Equivalent to the built-in function `sha256` in scrypt -export function sha256(hexstr: string, encoding?: BufferEncoding): string { - return bsv.crypto.Hash.sha256(Buffer.from(hexstr, encoding || 'hex')).toString('hex'); +export function sha256(hexstr: string, encoding?: 'hex' | 'utf8'): string { + return Utils.toHex(Hash.sha256(hexstr, encoding || 'hex')); } // Equivalent to the built-in function `hash256` in scrypt -export function hash256(hexstr: string, encoding?: BufferEncoding): string { - return sha256(sha256(hexstr, encoding), encoding); +export function hash256(hexstr: string, encoding?: 'hex' | 'utf8'): string { + return Utils.toHex(Hash.hash256(hexstr, encoding || 'hex')); } \ No newline at end of file diff --git a/src/compilerWrapper.ts b/src/compilerWrapper.ts index 481a7977..419ce331 100644 --- a/src/compilerWrapper.ts +++ b/src/compilerWrapper.ts @@ -230,7 +230,7 @@ export interface CompilingSettings { } function toOutputDir(artifactsDir: string, sourcePath: string) { - return join(artifactsDir, basename(sourcePath) + '-' + hash160(sourcePath, 'utf-8').substring(0, 10)); + return join(artifactsDir, basename(sourcePath) + '-' + hash160(sourcePath, 'utf8').substring(0, 10)); } export function doCompileAsync(source: { path: string, diff --git a/src/contract.ts b/src/contract.ts index 52ecf391..8088a5dd 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -2,12 +2,12 @@ import { basename, dirname } from 'path'; import { ABIEntityType, Argument, LibraryEntity, ParamEntity, parseGenericType } from '.'; import { ContractEntity, getFullFilePath, loadSourceMapfromArtifact, OpCode, StaticEntity } from './compilerWrapper'; import { - ABICoder, ABIEntity, AliasEntity, Arguments, bsv, buildContractCode, checkNOPScript, CompileResult, DEFAULT_FLAGS, findSrcInfoV1, findSrcInfoV2, FunctionCall, hash160, isArrayType, JSONParserSync, path2uri, resolveType, Script, StructEntity, subscript, TypeResolver, uri2path + ABICoder, ABIEntity, addScript, AliasEntity, Arguments, buildContractCode, checkNOPScript, CompileResult, findSrcInfoV1, findSrcInfoV2, FunctionCall, hash160, isArrayType, JSONParserSync, path2uri, resolveType, sha256, StructEntity, subscript, TypeResolver, uri2path } from './internal'; import { Bytes, Int, isScryptType, SupportedParamType, SymbolType, TypeInfo } from './scryptTypes'; import Stateful from './stateful'; import { arrayTypeAndSize, checkSupportedParamType, flatternArg, hasGeneric, subArrayType } from './typeCheck'; - +import { BigNumber, OP, Script, Transaction, UnlockingScript, Utils, Spend } from '@bsv/sdk'; /** * TxContext provides some context information of the current transaction, @@ -15,7 +15,7 @@ import { arrayTypeAndSize, checkSupportedParamType, flatternArg, hasGeneric, sub */ export interface TxContext { /** current transaction represented in bsv.Transaction object or hex string */ - tx: bsv.Transaction | string; + tx: Transaction | string; /** input index */ inputIndex: number; /** input amount in satoshis */ @@ -78,7 +78,7 @@ export interface Artifact { /** NOPScript */ -export type NOPScript = bsv.Script; +export type NOPScript = Script; export type AsmVarValues = { [key: string]: string } @@ -125,16 +125,18 @@ export class AbstractContract { } if (!this.dataPart) { - return this._wrapNOPScript(this.scriptedConstructor?.lockingScript as Script); + return this._wrapNOPScript(this.scriptedConstructor?.lockingScript); } // append dataPart script to codePart if there is dataPart - return this.codePart.add(this.dataPart); + return addScript(this.codePart, this.dataPart); } - private _wrapNOPScript(lockingScript: bsv.Script) { + private _wrapNOPScript(lockingScript: Script) { if (this.nopScript) { - return this.nopScript.clone().add(lockingScript); + const clone = Script.fromBinary(this.nopScript.toBinary()) + + return addScript(clone, lockingScript); } return lockingScript; @@ -190,7 +192,7 @@ export class AbstractContract { if (asmVarValues) { for (const key in asmVarValues) { const val = asmVarValues[key]; - this.hexTemplateInlineASM.set(`<${key.startsWith('$') ? key.substring(1) : key}>`, bsv.Script.fromASM(val).toHex()); + this.hexTemplateInlineASM.set(`<${key.startsWith('$') ? key.substring(1) : key}>`, Script.fromASM(val).toHex()); } } @@ -210,7 +212,7 @@ export class AbstractContract { for (const entry of this.hexTemplateInlineASM.entries()) { const name = entry[0].replace('<', '').replace('>', ''); const value = entry[1]; - result[name] = bsv.Script.fromHex(value).toASM(); + result[name] = Script.fromHex(value).toASM(); } return result; @@ -261,68 +263,118 @@ export class AbstractContract { } }); - return this.codePart.add(bsv.Script.fromHex(Stateful.buildState(newState, false, this.resolver))); + return addScript(this.codePart, Script.fromHex(Stateful.buildState(newState, false, this.resolver))); } - run_verify(unlockingScript: bsv.Script | string | undefined, txContext?: TxContext): VerifyResult { - const txCtx = Object.assign({}, this._txContext || {}, txContext || {}) as TxContext; - let us; - if (typeof unlockingScript === 'string') { - us = unlockingScript.trim() ? bsv.Script.fromASM(unlockingScript.trim()) : new bsv.Script(''); + run_verify(unlockingScript: UnlockingScript): VerifyResult { + + let tx: Transaction; + + if (this._txContext && this._txContext.tx) { + tx = typeof this._txContext.tx === 'string' ? Transaction.fromHex(this._txContext.tx) : this._txContext.tx; } else { - us = unlockingScript ? unlockingScript : new bsv.Script(''); + const sourceTx = new Transaction(1, [], [{ + lockingScript: this.lockingScript, + satoshis: 100000 + }], 0) + + tx = new Transaction(1, [{ + sourceTransaction: sourceTx, + sourceOutputIndex: 0, + sequence: 0xffffffff + }], [], 0) } - const ls = bsv.Script.fromHex(this.lockingScript.toHex()); - const tx = typeof txCtx.tx === 'string' ? new bsv.Transaction(txCtx.tx) : txCtx.tx; - const inputIndex = txCtx.inputIndex; - const inputSatoshis = txCtx.inputSatoshis; + const inputIndex = this._txContext?.inputIndex || 0; - bsv.Script.Interpreter.MAX_SCRIPT_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; - bsv.Script.Interpreter.MAXIMUM_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; + const input = tx.inputs[inputIndex]; + const sourceSatoshis = this._txContext?.inputSatoshis || input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis - const bsi = new bsv.Script.Interpreter(); + const sourceTXID = tx.inputs[0].sourceTXID || tx.inputs[0].sourceTransaction.id('hex'); - let failedAt: any = {}; + const otherInputs = tx.inputs.filter((_, index) => index !== inputIndex) + const spend = new Spend({ + sourceTXID: sourceTXID as string, + sourceOutputIndex: input.sourceOutputIndex || 0, + sourceSatoshis: sourceSatoshis, + lockingScript: this.lockingScript, + transactionVersion: tx.version, + otherInputs: otherInputs, + inputIndex: inputIndex, + unlockingScript, + outputs: tx.outputs, + inputSequence: input.sequence, + lockTime: tx.lockTime, + }) - bsi.stepListener = function (step: any) { - if (step.fExec || (bsv.Opcode.OP_IF <= step.opcode.toNumber() && step.opcode.toNumber() <= bsv.Opcode.OP_ENDIF)) { - if ((bsv.Opcode.OP_IF <= step.opcode.toNumber() && step.opcode.toNumber() <= bsv.Opcode.OP_ENDIF) || step.opcode.toNumber() === bsv.Opcode.OP_RETURN) /**Opreturn */ { - failedAt.opcode = step.opcode; - } else { - failedAt = step; + try { + const valid = spend.validate() + + + return { + success: valid + } + } catch (error) { + + if ((error.message || '').indexOf('OP_CHECKSIG failed to verify the signature') > -1) { + if (!this.txContext) { + throw new Error('should provide txContext when verify'); } } - }; - const result = bsi.verify(us, ls, tx, inputIndex, DEFAULT_FLAGS, new bsv.crypto.BN(inputSatoshis)); - if (result) { return { - success: true, - error: '' - }; - } - - if ((bsi.errstr || '').indexOf('SCRIPT_ERR_NULLFAIL') > -1) { - if (!txCtx) { - throw new Error('should provide txContext when verify'); - } if (!tx) { - throw new Error('should provide txContext.tx when verify'); + success: false, + error: error.message } } - failedAt.opcode = failedAt.opcode.toNumber(); + // Script.Interpreter.MAX_SCRIPT_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; + // Script.Interpreter.MAXIMUM_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; - return { - success: result, - error: this.fmtError({ - error: bsi.errstr || '', - failedAt - }) - }; + + // const bsi = new Script.Interpreter(); + + // let failedAt: any = {}; + + // bsi.stepListener = function (step: any) { + // if (step.fExec || (OP.OP_IF <= step.opcode.toNumber() && step.opcode.toNumber() <= OP.OP_ENDIF)) { + // if ((OP.OP_IF <= step.opcode.toNumber() && step.opcode.toNumber() <= OP.OP_ENDIF) || step.opcode.toNumber() === OP.OP_RETURN) /**Opreturn */ { + // failedAt.opcode = step.opcode; + // } else { + // failedAt = step; + // } + // } + // }; + + // const result = bsi.verify(us, ls, tx, inputIndex, DEFAULT_FLAGS, new bsv.crypto.BN(inputSatoshis)); + // if (result) { + // return { + // success: true, + // error: '' + // }; + // } + + // if ((bsi.errstr || '').indexOf('SCRIPT_ERR_NULLFAIL') > -1) { + // if (!txCtx) { + // throw new Error('should provide txContext when verify'); + // } if (!tx) { + // throw new Error('should provide txContext.tx when verify'); + // } + // } + + + // failedAt.opcode = failedAt.opcode.toNumber(); + + // return { + // success: result, + // error: this.fmtError({ + // error: bsi.errstr || '', + // failedAt + // }) + // }; } /** @@ -340,7 +392,7 @@ export class AbstractContract { }): string { const failedOpCode: number = err.failedAt.opcode; - let error = `VerifyError: ${err.error}, fails at ${new bsv.Opcode(failedOpCode)}\n`; + let error = `VerifyError: ${err.error}, fails at ${OP[failedOpCode]}\n`; if (this.sourceMapFile) { const sourceMapFilePath = uri2path(this.sourceMapFile); @@ -356,7 +408,7 @@ export class AbstractContract { const pos = findSrcInfoV2(err.failedAt.pc, sourceMap); if (pos && sources[pos[1]]) { - error = `VerifyError: ${err.error} \n\t[Go to Source](${path2uri(sources[pos[1]])}#${pos[2]}) fails at ${new bsv.Opcode(failedOpCode)}\n`; + error = `VerifyError: ${err.error} \n\t[Go to Source](${path2uri(sources[pos[1]])}#${pos[2]}) fails at ${OP[failedOpCode]}\n`; } } else if (this.version <= 8) { @@ -386,7 +438,7 @@ export class AbstractContract { // in vscode termianal need to use [:] to jump to file line, but here need to use [#] to jump to file line in output channel. if (opcode && opcode.pos) { - error = `VerifyError: ${err.error} \n\t[Go to Source](${path2uri(opcode.pos.file)}#${opcode.pos.line}) fails at ${new bsv.Opcode(failedOpCode)}\n`; + error = `VerifyError: ${err.error} \n\t[Go to Source](${path2uri(opcode.pos.file)}#${opcode.pos.line}) fails at ${OP[failedOpCode]}\n`; } } } @@ -400,14 +452,12 @@ export class AbstractContract { * @param txContext * @returns a uri of the debugger launch configuration */ - public genLaunchConfig(txContext?: TxContext): string { - - const txCtx = Object.assign({}, this.txContext || {}, txContext || {}) as TxContext; + public genLaunchConfig(): string { const lastCalledPubFunction = this.lastCalledPubFunction(); if (lastCalledPubFunction) { - const debugUrl = lastCalledPubFunction.genLaunchConfig(txCtx); + const debugUrl = lastCalledPubFunction.genLaunchConfig(); return `[Launch Debugger](${debugUrl.replace(/file:/i, 'scryptlaunch:')})\n`; } throw new Error('No public function called'); @@ -425,11 +475,11 @@ export class AbstractContract { if (AbstractContract.isStateful(this)) { const state = Stateful.buildState(this.statePropsArgs, this.isGenesis, this.resolver); - return bsv.Script.fromHex(state); + return Script.fromHex(state); } if (this._dataPartInHex) { - return bsv.Script.fromHex(this._dataPartInHex); + return Script.fromHex(this._dataPartInHex); } } @@ -460,7 +510,7 @@ export class AbstractContract { throw new Error('should not use `setDataPartInASM` for a stateful contract, using `setDataPartInHex`'); } const dataPartInASM = asm.trim(); - this.setDataPartInHex(bsv.Script.fromASM(dataPartInASM).toHex()); + this.setDataPartInHex(Script.fromASM(dataPartInASM).toHex()); } /** @@ -477,7 +527,7 @@ export class AbstractContract { } prependNOPScript(nopScript: NOPScript | null): void { - if (nopScript instanceof bsv.Script) { + if (nopScript instanceof Script) { checkNOPScript(nopScript); } @@ -491,7 +541,8 @@ export class AbstractContract { get codePart(): Script { const contractScript = this.scriptedConstructor.toScript(); // note: do not trim the trailing space - return this._wrapNOPScript(contractScript.clone()).add(bsv.Script.fromHex('6a')); + const clone = Script.fromBinary(contractScript.toBinary()) + return addScript(this._wrapNOPScript(clone), Script.fromHex('6a')); } get codeHash(): string { @@ -686,7 +737,7 @@ export class AbstractContract { static fromASM(asm: string): AbstractContract { - return this.fromHex(bsv.Script.fromASM(asm).toHex()); + return this.fromHex(Script.fromASM(asm).toHex()); } static fromHex(hex: string): AbstractContract { @@ -700,8 +751,8 @@ export class AbstractContract { static fromTransaction(hex: string, outputIndex = 0): AbstractContract { - const tx = new bsv.Transaction(hex); - return this.fromHex(tx.outputs[outputIndex].script.toHex()); + const tx = Transaction.fromHex(hex); + return this.fromHex(tx.outputs[outputIndex].lockingScript.toHex()); } static isStateful(contract: AbstractContract): boolean { @@ -731,47 +782,38 @@ export class AbstractContract { if (flattened.length === 1) { const hex = Stateful.serialize(flattened[0].value, flattened[0].type); - return bsv.crypto.Hash.sha256(Buffer.from(hex, 'hex')).toString('hex'); + return sha256(hex); } else { const jointbytes = flattened.map(item => { const hex = Stateful.serialize(item.value, item.type); - return bsv.crypto.Hash.sha256(Buffer.from(hex, 'hex')).toString('hex'); + return sha256(hex); }).join(''); - return bsv.crypto.Hash.sha256(Buffer.from(jointbytes, 'hex')).toString('hex'); + return sha256(jointbytes); } } // sort the map by the result of flattenSha256 of the key private static sortmap(map: Map, keyType: string): Map { return new Map([...map.entries()].sort((a, b) => { - return bsv.crypto.BN.fromSM(Buffer.from(this.flattenSha256(a[0], keyType), 'hex'), { - endian: 'little' - }).cmp(bsv.crypto.BN.fromSM(Buffer.from(this.flattenSha256(b[0], keyType), 'hex'), { - endian: 'little' - })); + return BigNumber.fromSm(Utils.toArray(this.flattenSha256(a[0], keyType)), 'little') + .cmp(BigNumber.fromSm(Utils.toArray(this.flattenSha256(b[0], keyType)), 'little')); })); } // sort the set by the result of flattenSha256 of the key private static sortset(set: Set, keyType: string): Set { return new Set([...set.keys()].sort((a, b) => { - return bsv.crypto.BN.fromSM(Buffer.from(this.flattenSha256(a, keyType), 'hex'), { - endian: 'little' - }).cmp(bsv.crypto.BN.fromSM(Buffer.from(this.flattenSha256(b, keyType), 'hex'), { - endian: 'little' - })); + return BigNumber.fromSm(Utils.toArray(this.flattenSha256(a, keyType)), 'little'). + cmp(BigNumber.fromSm(Utils.toArray(this.flattenSha256(b, keyType)), 'little')); })); } private static sortkeys(keys: SupportedParamType[], keyType: string): SupportedParamType[] { return keys.sort((a, b) => { - return bsv.crypto.BN.fromSM(Buffer.from(this.flattenSha256(a, keyType), 'hex'), { - endian: 'little' - }).cmp(bsv.crypto.BN.fromSM(Buffer.from(this.flattenSha256(b, keyType), 'hex'), { - endian: 'little' - })); + return BigNumber.fromSm(Utils.toArray(this.flattenSha256(a, keyType)), 'little') + .cmp(BigNumber.fromSm(Utils.toArray(this.flattenSha256(b, keyType)), 'little')); }); } diff --git a/src/deserializer.ts b/src/deserializer.ts index 9181aae8..703bff69 100644 --- a/src/deserializer.ts +++ b/src/deserializer.ts @@ -1,7 +1,8 @@ +import { Script, Utils } from '@bsv/sdk'; import { Argument, arrayTypeAndSize, bin2num, isArrayType, LibraryEntity, ParamEntity, StructEntity, SymbolType, TypeResolver } from '.'; import { Bool, Bytes, Int, OpCodeType, PrivKey, PubKey, Ripemd160, ScryptType, Sha1, Sha256, Sig, SigHashPreimage, SigHashType, StructObject, SupportedParamType } from './scryptTypes'; import Stateful from './stateful'; -import { bsv } from './utils'; + /** * little-endian signed magnitude to int @@ -13,13 +14,13 @@ export function hex2int(hex: string): bigint { } else if (hex === '4f') { return Int(-1); } else { - const b = bsv.Script.fromHex(hex); + const b = Script.fromHex(hex); const chuck = b.chunks[0]; - if (chuck.opcodenum >= 81 && chuck.opcodenum <= 96) { - return BigInt(chuck.opcodenum - 80); + if (chuck.op >= 81 && chuck.op <= 96) { + return BigInt(chuck.op - 80); } - return bin2num(chuck.buf.toString('hex')); + return bin2num(Utils.toHex(chuck.data)); } } @@ -39,14 +40,14 @@ export function hex2bytes(hex: string): Bytes { return ''; } - const s = bsv.Script.fromHex(hex); + const s = Script.fromHex(hex); const chuck = s.chunks[0]; - if (chuck.opcodenum >= 81 && chuck.opcodenum <= 96) { - return Buffer.from([chuck.opcodenum - 80]).toString('hex'); + if (chuck.op >= 81 && chuck.op <= 96) { + return Utils.toHex([chuck.op - 80]) } - return chuck.buf.toString('hex'); + return Utils.toHex(chuck.data); } export function deserializer(type: string, hex: string): SupportedParamType { diff --git a/src/index.ts b/src/index.ts index d16ce9e6..953e080e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export { } from './internal'; export { - bsv, toHex, bin2num, int2Asm, bytes2Literal, bytesToHexString, getValidatedHexString, + toHex, bin2num, int2Asm, bytes2Literal, bytesToHexString, getValidatedHexString, findStructByType, findStructByName, isArrayType, arrayTypeAndSize, newCall, getNameByType, genLaunchConfigFile, subArrayType, isGenericType, parseGenericType, @@ -23,7 +23,7 @@ export { StructObject, TypeResolver, PrimitiveTypes, AsmVarValues, Flavor, Arguments, Argument, StructEntity, LibraryEntity, ABIEntity, ABIEntityType, ABI, ParamEntity, BuildType, RelatedInformation, Artifact, VerifyResult, VerifyError, AbstractContract, - DebugInfo, DebugModeTag, ContractEntity, TypeInfo, SymbolType, DEFAULT_FLAGS, ScryptType, DEFAULT_SIGHASH_TYPE, CallData, + DebugInfo, DebugModeTag, ContractEntity, TypeInfo, SymbolType, ScryptType, CallData, NOPScript } from './internal'; diff --git a/src/launchConfig.ts b/src/launchConfig.ts index 0bcd73ad..b9ee55e7 100644 --- a/src/launchConfig.ts +++ b/src/launchConfig.ts @@ -142,7 +142,7 @@ export function toJSON(arg: Argument, resolver: TypeResolver): unknown { case ScryptType.BOOL: return arg.value; case ScryptType.INT: { - if (arg.value >= BigInt(Number.MIN_SAFE_INTEGER) && arg.value <= BigInt(Number.MAX_SAFE_INTEGER)) { + if (BigInt(arg.value as bigint) >= BigInt(Number.MIN_SAFE_INTEGER) && BigInt(arg.value as bigint) <= BigInt(Number.MAX_SAFE_INTEGER)) { return Number(arg.value); } else { return arg.value.toString(); diff --git a/src/serializer.ts b/src/serializer.ts index 4b205ac3..fbaf5f78 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -1,8 +1,8 @@ +import { BigNumber, Script, Utils } from '@bsv/sdk'; import { Bool, Bytes, Int, isBytes, ScryptType, SupportedParamType } from './scryptTypes'; -import { bsv } from './utils'; -const BN = bsv.crypto.BN; +const BN = BigNumber /** @@ -17,9 +17,9 @@ export function int2hex(n: Int): string { n += Int(80); return n.toString(16); } - const number = new BN(n); - const m = number.toSM({ endian: 'little' }); - return bsv.Script.fromASM(m.toString('hex')).toHex(); + const number = new BN(n.toString().replace(/n/, '')); + const m = number.toSm('little'); + return Script.fromASM(Utils.toHex(m)).toHex(); } @@ -36,7 +36,7 @@ export function bytes2hex(b: Bytes): string { if (b) { if (b.length / 2 > 1) { - return bsv.Script.fromASM(b).toHex(); + return Script.fromASM(b).toHex(); } const intValue = parseInt(b, 16); @@ -45,7 +45,7 @@ export function bytes2hex(b: Bytes): string { return BigInt(intValue + 80).toString(16); } - return bsv.Script.fromASM(b).toHex(); + return Script.fromASM(b).toHex(); } return '00'; } @@ -67,5 +67,5 @@ export function toScriptHex(x: SupportedParamType, type: string): string { export function toScriptASM(a: SupportedParamType, type: string): string { const hex = toScriptHex(a, type); - return bsv.Script.fromHex(hex).toASM(); + return Script.fromHex(hex).toASM(); } diff --git a/src/stateful.ts b/src/stateful.ts index 3d4602d9..4230a1b3 100644 --- a/src/stateful.ts +++ b/src/stateful.ts @@ -1,8 +1,8 @@ -import { AbstractContract, Arguments, bin2num, bsv, num2bin } from '.'; +import { BigNumber, OP, Script, Utils } from '@bsv/sdk'; +import { AbstractContract, Arguments, bin2num, num2bin } from '.'; import { deserializeArgfromHex } from './deserializer'; import { flatternArg } from './internal'; import { Bool, Bytes, Int, isBytes, OpCodeType, PrivKey, PubKey, Ripemd160, ScryptType, Sha1, Sha256, Sig, SigHashPreimage, SigHashType, SupportedParamType, TypeResolver } from './scryptTypes'; - export default class Stateful { // state version @@ -10,20 +10,20 @@ export default class Stateful { static int2hex(n: Int): string { let asm = ''; - const num = new bsv.crypto.BN(n); + const num = new BigNumber(n.toString().replace(/n/, '')); if (num.eqn(0)) { asm = '00'; } else { - asm = num.toSM({ endian: 'little' }).toString('hex'); + asm = Utils.toHex(num.toSm('little')); } - return bsv.Script.fromASM(asm).toHex(); + return Script.fromASM(asm).toHex(); } static hex2int(hex: string): bigint { - const s = bsv.Script.fromHex(hex); + const s = Script.fromHex(hex); const chuck = s.chunks[0]; - return bin2num(chuck.buf.toString('hex')); + return bin2num(Utils.toHex(chuck.data)); } @@ -48,16 +48,16 @@ export default class Stateful { if (b === '') { return '00'; } - return bsv.Script.fromASM(b).toHex(); + return Script.fromASM(b).toHex(); } static hex2bytes(hex: string): Bytes { if (hex === '00') { return ''; } - const s = bsv.Script.fromHex(hex); + const s = Script.fromHex(hex); const chuck = s.chunks[0]; - return chuck.buf.toString('hex'); + return Utils.toHex(chuck.data); } static toHex(x: SupportedParamType, type: string): string { @@ -76,11 +76,11 @@ export default class Stateful { static serialize(x: SupportedParamType, type: string): string { if (type === ScryptType.INT || type === ScryptType.PRIVKEY) { - const num = new bsv.crypto.BN(x as bigint); + const num = new BigNumber(x.toString().replace(/n/, '')); if (num.eqn(0)) { return ''; } else { - return num.toSM({ endian: 'little' }).toString('hex'); + return Utils.toHex(num.toSm('little')); } } else if (type === ScryptType.BOOL) { if (x) { @@ -204,7 +204,7 @@ export default class Stateful { - static readBytes(br: bsv.encoding.BufferReader): { + static readBytes(br: Utils.Reader): { data: string, opcodenum: number } { @@ -214,18 +214,18 @@ export default class Stateful { let len, data; if (opcodenum == 0) { data = ''; - } else if (opcodenum > 0 && opcodenum < bsv.Opcode.OP_PUSHDATA1) { + } else if (opcodenum > 0 && opcodenum < OP.OP_PUSHDATA1) { len = opcodenum; - data = br.read(len).toString('hex'); - } else if (opcodenum === bsv.Opcode.OP_PUSHDATA1) { + data = Utils.toHex(br.read(len)); + } else if (opcodenum === OP.OP_PUSHDATA1) { len = br.readUInt8(); - data = br.read(len).toString('hex'); - } else if (opcodenum === bsv.Opcode.OP_PUSHDATA2) { + data = Utils.toHex(br.read(len)); + } else if (opcodenum === OP.OP_PUSHDATA2) { len = br.readUInt16LE(); - data = br.read(len).toString('hex'); - } else if (opcodenum === bsv.Opcode.OP_PUSHDATA4) { + data = Utils.toHex(br.read(len)); + } else if (opcodenum === OP.OP_PUSHDATA4) { len = br.readUInt32LE(); - data = br.read(len).toString('hex'); + data = Utils.toHex(br.read(len)); } else { data = num2bin(BigInt(opcodenum - 80), 1); } @@ -250,7 +250,7 @@ export default class Stateful { const stateHex = scriptHex.substr(scriptHex.length - 10 - stateLen * 2, stateLen * 2); - const br = new bsv.encoding.BufferReader(Buffer.from(stateHex, 'hex')); + const br = new Utils.Reader(Utils.toArray(stateHex, 'hex')); const opcodenum = br.readUInt8(); @@ -270,7 +270,7 @@ export default class Stateful { stateTemplateArgs.set(`<${param.name}>`, opcodenum === 1 ? '01' : '00'); } else { const { data } = Stateful.readBytes(br); - stateTemplateArgs.set(`<${param.name}>`, data ? bsv.Script.fromASM(data).toHex() : '00'); + stateTemplateArgs.set(`<${param.name}>`, data ? Script.fromASM(data).toHex() : '00'); } }); diff --git a/src/utils.ts b/src/utils.ts index 7db09f96..a4da51c0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,34 +1,14 @@ import { parseChunked, stringifyStream } from '@discoveryjs/json-ext'; -import * as bsv from 'bsv'; import * as crypto from 'crypto'; import * as fs from 'fs'; import { join } from 'path'; import { decode } from '@jridgewell/sourcemap-codec'; import { fileURLToPath, pathToFileURL } from 'url'; - -export { bsv }; - import { ABIEntity, LibraryEntity } from '.'; import { compileAsync, OpCode } from './compilerWrapper'; -import { AbstractContract, compile, CompileResult, findCompiler, getValidatedHexString, Script, ScryptType, StructEntity, SupportedParamType } from './internal'; +import { AbstractContract, compile, CompileResult, findCompiler, getValidatedHexString, ScryptType, StructEntity, SupportedParamType, } from './internal'; import { arrayTypeAndSizeStr, isGenericType, parseGenericType } from './typeCheck'; - -const BN = bsv.crypto.BN; -const Interp = bsv.Script.Interpreter; - -export const DEFAULT_FLAGS = - //Interp.SCRIPT_VERIFY_P2SH | Interp.SCRIPT_VERIFY_CLEANSTACK | // no longer applies now p2sh is deprecated: cleanstack only applies to p2sh - Interp.SCRIPT_ENABLE_MAGNETIC_OPCODES | Interp.SCRIPT_ENABLE_MONOLITH_OPCODES | // TODO: to be removed after upgrade to bsv 2.0 - Interp.SCRIPT_VERIFY_STRICTENC | - Interp.SCRIPT_ENABLE_SIGHASH_FORKID | Interp.SCRIPT_VERIFY_LOW_S | Interp.SCRIPT_VERIFY_NULLFAIL | - Interp.SCRIPT_VERIFY_DERSIG | - Interp.SCRIPT_VERIFY_MINIMALDATA | Interp.SCRIPT_VERIFY_NULLDUMMY | - Interp.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | - Interp.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | Interp.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | Interp.SCRIPT_VERIFY_CLEANSTACK; - -export const DEFAULT_SIGHASH_TYPE = - bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID; - +import { PrivateKey, Transaction, BigNumber, LockingScript, TransactionSignature, Hash, UnlockingScript, Script, Utils } from '@bsv/sdk' /** * decimal or hex int to little-endian signed magnitude @@ -37,14 +17,13 @@ export function int2Asm(str: string): string { if (/^(-?\d+)$/.test(str) || /^0x([0-9a-fA-F]+)$/.test(str)) { - const number = str.startsWith('0x') ? new BN(str.substring(2), 16) : new BN(str, 10); + const number = str.startsWith('0x') ? new BigNumber(str.substring(2), 16) : new BigNumber(str, 10); if (number.eqn(-1)) { return 'OP_1NEGATE'; } if (number.gten(0) && number.lten(16)) { return 'OP_' + number.toString(); } - const m = number.toSM({ endian: 'little' }); - return m.toString('hex'); + return number.toHex(); } else { throw new Error(`invalid str '${str}' to convert to int`); @@ -82,9 +61,7 @@ export function asm2int(str: string): number | string { return parseInt(str.replace('OP_', '')); default: { const value = getValidatedHexString(str); - const bn = BN.fromHex(value, { - endian: 'little' - }); + const bn = BigNumber.fromHex(value, 'little'); if (bn.toNumber() < Number.MAX_SAFE_INTEGER && bn.toNumber() > Number.MIN_SAFE_INTEGER) { return bn.toNumber(); @@ -129,11 +106,11 @@ export function bytes2Literal(bytearray: Buffer, type: string): string { switch (type) { case 'bool': - return BN.fromBuffer(bytearray, { endian: 'little' }).gt(0) ? 'true' : 'false'; + return Array.from(bytearray)[0] !== 0 ? 'true' : 'false'; case 'int': case 'PrivKey': - return BN.fromSM(bytearray, { endian: 'little' }).toString(); + return BigNumber.fromSm(Array.from(bytearray), "little").toString(); case 'bytes': return `b'${bytesToHexString(bytearray)}'`; @@ -165,7 +142,7 @@ export function hexStringToBytes(hex: string): number[] { } -export function signTx(tx: bsv.Transaction, privateKey: bsv.PrivateKey, lockingScript: Script, inputAmount: number, inputIndex = 0, sighashType = DEFAULT_SIGHASH_TYPE, flags = DEFAULT_FLAGS): string { +export function signTx(tx: Transaction, privateKey: PrivateKey, subscript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number = 65): string { if (!tx) { throw new Error('param tx can not be empty'); @@ -175,50 +152,74 @@ export function signTx(tx: bsv.Transaction, privateKey: bsv.PrivateKey, lockingS throw new Error('param privateKey can not be empty'); } - if (!lockingScript) { - throw new Error('param lockingScript can not be empty'); - } - if (!inputAmount) { throw new Error('param inputAmount can not be empty'); } - if (typeof lockingScript === 'string') { - throw new Error('Breaking change: LockingScript in ASM format is no longer supported, please use the lockingScript object directly'); - } + const preimage = getPreimage(tx, subscript, inputAmount, inputIndex, sighashType); - return toHex(bsv.Transaction.Sighash.sign( - tx, privateKey, sighashType, inputIndex, - lockingScript, new bsv.crypto.BN(inputAmount), flags - ).toTxFormat()); + const rawSignature = privateKey.sign(Hash.sha256(preimage, 'hex')) + const sig = new TransactionSignature( + rawSignature.r, + rawSignature.s, + sighashType + ) + const sigForScript = sig.toChecksigFormat() + + return Utils.toHex(sigForScript); } -export function getPreimage(tx: bsv.Transaction, lockingScript: Script, inputAmount: number, inputIndex = 0, sighashType = DEFAULT_SIGHASH_TYPE, flags = DEFAULT_FLAGS): string { - const preimageBuf = bsv.Transaction.Sighash.sighashPreimage(tx, sighashType, inputIndex, lockingScript, new bsv.crypto.BN(inputAmount), flags); - return toHex(preimageBuf); +export function getPreimage(tx: Transaction, subscript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number): string { + + const input = tx.inputs[inputIndex] + const otherInputs = tx.inputs.filter((_, index) => index !== inputIndex) + + const sourceTXID = input.sourceTXID || input.sourceTransaction?.id('hex') as string; + if (!sourceTXID) { + // Question: Should the library support use-cases where the source transaction is not provided? This is to say, is it ever acceptable for someone to sign an input spending some output from a transaction they have not provided? Some elements (such as the satoshi value and output script) are always required. A merkle proof is also always required, and verifying it (while also verifying that the claimed output is contained within the claimed transaction) is also always required. This seems to require the entire input transaction. + throw new Error( + 'The source transaction is needed for transaction signing.' + ) + } + + const preimage = TransactionSignature.format({ + sourceTXID: sourceTXID, + sourceOutputIndex: input.sourceOutputIndex, + sourceSatoshis: inputAmount, + transactionVersion: tx.version, + otherInputs, + inputIndex, + outputs: tx.outputs, + inputSequence: input.sequence, + subscript: subscript, + lockTime: tx.lockTime, + scope: sighashType + }) + + return Utils.toHex(preimage); } const MSB_THRESHOLD = 0x7e; -export function hashIsPositiveNumber(sighash: Buffer): boolean { - const highByte = sighash.readUInt8(31); +export function hashIsPositiveNumber(sighash: number[]): boolean { + const highByte = sighash[31]; return highByte < MSB_THRESHOLD; } -export function getLowSPreimage(tx: bsv.Transaction, lockingScript: Script, inputAmount: number, inputIndex = 0, sighashType = DEFAULT_SIGHASH_TYPE, flags = DEFAULT_FLAGS): string { +export function getLowSPreimage(tx: Transaction, lockingScript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number): string { for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) { - const preimage = getPreimage(tx, lockingScript, inputAmount, inputIndex, sighashType, flags); - const sighash = bsv.crypto.Hash.sha256sha256(Buffer.from(preimage, 'hex')); - const msb = sighash.readUInt8(); + const preimage = getPreimage(tx, lockingScript, inputAmount, inputIndex, sighashType); + const sighash = Hash.hash256(preimage); + const msb = sighash[0] if (msb < MSB_THRESHOLD && hashIsPositiveNumber(sighash)) { return preimage; } - tx.inputs[inputIndex].sequenceNumber--; + tx.inputs[inputIndex].sequence--; } } @@ -450,7 +451,7 @@ function escapeRegExp(stringToGoIntoTheRegex: string) { -export function buildContractCode(hexTemplateArgs: Map, hexTemplateInlineASM: Map, hexTemplate: string): bsv.Script { +export function buildContractCode(hexTemplateArgs: Map, hexTemplateInlineASM: Map, hexTemplate: string): LockingScript { let lsHex = hexTemplate; @@ -468,7 +469,7 @@ export function buildContractCode(hexTemplateArgs: Map, hexTempl lsHex = lsHex.replace(new RegExp(`${escapeRegExp(name)}`, 'g'), value); } - return bsv.Script.fromHex(lsHex); + return LockingScript.fromHex(lsHex); } @@ -493,7 +494,7 @@ export function parseAbiFromUnlockingScript(contract: AbstractContract, hex: str return pubFunAbis[0]; } - const script = bsv.Script.fromHex(hex); + const script = UnlockingScript.fromHex(hex); const usASM = script.toASM() as string; @@ -599,23 +600,28 @@ export function md5(s: string): string { } -export function checkNOPScript(nopScript: bsv.Script) { +export function checkNOPScript(nopScript: Script) { - bsv.Script.Interpreter.MAX_SCRIPT_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; - bsv.Script.Interpreter.MAXIMUM_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; - const bsi = new bsv.Script.Interpreter(); - const tx = new bsv.Transaction().from({ - txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', - outputIndex: 0, - script: '', // placeholder - satoshis: 1 - }); + // const bsi = new bsv.Script.Interpreter(); + // const tx = new Transaction(1, [{ + // sourceTXID: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458' + // }]).from({ + // txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', + // outputIndex: 0, + // script: '', // placeholder + // satoshis: 1 + // }); - const result = bsi.verify(new bsv.Script(""), nopScript, tx, 0, DEFAULT_FLAGS, new bsv.crypto.BN(1)); + // const result = bsi.verify(new bsv.Script(""), nopScript, tx, 0, DEFAULT_FLAGS, new bsv.crypto.BN(1)); - if (result || bsi.errstr !== "SCRIPT_ERR_EVAL_FALSE_NO_RESULT") { - throw new Error("NopScript should be a script that does not affect the Bitcoin virtual machine stack."); - } + // if (result || bsi.errstr !== "SCRIPT_ERR_EVAL_FALSE_NO_RESULT") { + // throw new Error("NopScript should be a script that does not affect the Bitcoin virtual machine stack."); + // } } + +export function addScript(a: Script, b: Script): Script { + const merged = a.chunks.concat(b.chunks); + return new Script(merged); +} \ No newline at end of file From 239d731bb34daa68ed1b3b7d012e104ab21c60c5 Mon Sep 17 00:00:00 2001 From: hh Date: Wed, 10 Apr 2024 10:55:53 +0800 Subject: [PATCH 2/3] add factory --- package-lock.json | 335 ++---------------- package.json | 11 +- src/abi.ts | 40 +-- src/builtins.ts | 73 +--- src/chain/base/factory.ts | 116 ++++++ src/chain/base/index.ts | 19 + src/chain/base/iop.ts | 208 +++++++++++ src/chain/base/primitives/BasePoint.ts | 8 + src/chain/base/primitives/BigNumber.ts | 67 ++++ src/chain/base/primitives/Point.ts | 23 ++ src/chain/base/primitives/PrivateKey.ts | 19 + src/chain/base/primitives/PublicKey.ts | 12 + src/chain/base/primitives/Reader.ts | 26 ++ src/chain/base/primitives/Signature.ts | 10 + src/chain/base/primitives/Writer.ts | 37 ++ src/chain/base/script/LockingScript.ts | 5 + src/chain/base/script/OP.ts | 217 ++++++++++++ src/chain/base/script/Script.ts | 40 +++ src/chain/base/script/UnlockingScript.ts | 5 + src/chain/base/transaction/Broadcaster.ts | 42 +++ src/chain/base/transaction/ChainTracker.ts | 22 ++ src/chain/base/transaction/FeeModel.ts | 12 + src/chain/base/transaction/MerklePath.ts | 17 + src/chain/base/transaction/Transaction.ts | 34 ++ .../base/transaction/TransactionInput.ts | 14 + .../base/transaction/TransactionOutput.ts | 36 ++ src/chain/bsv/factory.ts | 256 +++++++++++++ src/chain/bsv/index.ts | 1 + src/chain/bsv/primitives/PrivateKey.ts | 26 ++ src/chain/bsv/primitives/PublicKey.ts | 19 + src/chain/bsv/primitives/Reader.ts | 17 + src/chain/bsv/primitives/Writer.ts | 22 ++ src/chain/bsv/script/script.ts | 26 ++ src/chain/bsv/target.ts | 1 + src/chain/bsv/transaction/transaction.ts | 20 ++ src/chain/chain.ts | 44 +++ src/chain/index.ts | 2 + src/contract.ts | 37 +- src/utils.ts | 48 +-- test/abi.test.ts | 38 +- test/accumulatorMultiSig.test.ts | 98 ++--- test/counter.test.ts | 151 ++++---- test/helper.ts | 22 +- test/tx.test.ts | 26 ++ 44 files changed, 1719 insertions(+), 583 deletions(-) create mode 100644 src/chain/base/factory.ts create mode 100644 src/chain/base/index.ts create mode 100644 src/chain/base/iop.ts create mode 100644 src/chain/base/primitives/BasePoint.ts create mode 100644 src/chain/base/primitives/BigNumber.ts create mode 100644 src/chain/base/primitives/Point.ts create mode 100644 src/chain/base/primitives/PrivateKey.ts create mode 100644 src/chain/base/primitives/PublicKey.ts create mode 100644 src/chain/base/primitives/Reader.ts create mode 100644 src/chain/base/primitives/Signature.ts create mode 100644 src/chain/base/primitives/Writer.ts create mode 100644 src/chain/base/script/LockingScript.ts create mode 100644 src/chain/base/script/OP.ts create mode 100644 src/chain/base/script/Script.ts create mode 100644 src/chain/base/script/UnlockingScript.ts create mode 100644 src/chain/base/transaction/Broadcaster.ts create mode 100644 src/chain/base/transaction/ChainTracker.ts create mode 100644 src/chain/base/transaction/FeeModel.ts create mode 100644 src/chain/base/transaction/MerklePath.ts create mode 100644 src/chain/base/transaction/Transaction.ts create mode 100644 src/chain/base/transaction/TransactionInput.ts create mode 100644 src/chain/base/transaction/TransactionOutput.ts create mode 100644 src/chain/bsv/factory.ts create mode 100644 src/chain/bsv/index.ts create mode 100644 src/chain/bsv/primitives/PrivateKey.ts create mode 100644 src/chain/bsv/primitives/PublicKey.ts create mode 100644 src/chain/bsv/primitives/Reader.ts create mode 100644 src/chain/bsv/primitives/Writer.ts create mode 100644 src/chain/bsv/script/script.ts create mode 100644 src/chain/bsv/target.ts create mode 100644 src/chain/bsv/transaction/transaction.ts create mode 100644 src/chain/chain.ts create mode 100644 src/chain/index.ts create mode 100644 test/tx.test.ts diff --git a/package-lock.json b/package-lock.json index 024380f4..9512125b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,12 +7,11 @@ "": { "name": "scryptlib", "version": "2.1.41", - "hasInstallScript": true, "license": "MIT", "dependencies": { + "@bsv/sdk": "^1.0.13", "@discoveryjs/json-ext": "^0.5.7", "@jridgewell/sourcemap-codec": "^1.4.15", - "bsv": "^1.5.6", "chalk": "2.4.2", "compare-versions": "^3.6.0", "find-node-modules": "^2.1.3", @@ -42,7 +41,7 @@ "nyc": "^15.1.0", "ts-loader": "^6.2.1", "ts-node": "^8.6.2", - "typescript": "^4.9.5" + "typescript": "^5.4.3" }, "engines": { "node": ">=14.0.0" @@ -434,6 +433,11 @@ "node": ">=6.9.0" } }, + "node_modules/@bsv/sdk": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-1.0.13.tgz", + "integrity": "sha512-GuMQeYwN5umbrkgNucCQD3d/SfqiuJATtKI6l3HgimFuObITs/Ay1/oRVBiBzMDrqTyb0sFKcCYJwSaqbilSLw==" + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -958,11 +962,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/aes-js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", - "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==" - }, "node_modules/agent-base": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", @@ -1100,14 +1099,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -1154,11 +1145,6 @@ "node": ">=8" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -1188,34 +1174,6 @@ "url": "https://opencollective.com/browserslist" } }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bsv": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/bsv/-/bsv-1.5.6.tgz", - "integrity": "sha512-A0g36x63lVF9Ia6/z/RjcxaQMHE5cLl2rDxjUIKz0UTMLf5bPPyLI9yVyY2JkecF77MrU+MQdKVt0MSdU5abtw==", - "dependencies": { - "aes-js": "^3.1.2", - "bn.js": "=4.11.9", - "bs58": "=4.0.1", - "clone-deep": "^4.0.1", - "elliptic": "6.5.4", - "hash.js": "^1.1.7", - "inherits": "2.0.3", - "unorm": "1.4.1" - } - }, - "node_modules/bsv/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1382,19 +1340,6 @@ "node": ">=12" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1590,30 +1535,6 @@ "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==", "dev": true }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/elliptic/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -2549,15 +2470,6 @@ "node": ">=4" } }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -2574,16 +2486,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -2796,17 +2698,6 @@ "node": ">=8" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -2853,14 +2744,6 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", @@ -3173,14 +3056,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/klaw-sync": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", @@ -3370,16 +3245,6 @@ "node": ">=8.6" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4439,6 +4304,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -4468,17 +4334,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -4852,16 +4707,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/universalify": { @@ -4872,14 +4727,6 @@ "node": ">= 4.0.0" } }, - "node_modules/unorm": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz", - "integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA=", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5400,6 +5247,11 @@ "to-fast-properties": "^2.0.0" } }, + "@bsv/sdk": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-1.0.13.tgz", + "integrity": "sha512-GuMQeYwN5umbrkgNucCQD3d/SfqiuJATtKI6l3HgimFuObITs/Ay1/oRVBiBzMDrqTyb0sFKcCYJwSaqbilSLw==" + }, "@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -5756,11 +5608,6 @@ "dev": true, "requires": {} }, - "aes-js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", - "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==" - }, "agent-base": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", @@ -5867,14 +5714,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -5909,11 +5748,6 @@ "fill-range": "^7.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -5933,36 +5767,6 @@ "node-releases": "^1.1.71" } }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "requires": { - "base-x": "^3.0.2" - } - }, - "bsv": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/bsv/-/bsv-1.5.6.tgz", - "integrity": "sha512-A0g36x63lVF9Ia6/z/RjcxaQMHE5cLl2rDxjUIKz0UTMLf5bPPyLI9yVyY2JkecF77MrU+MQdKVt0MSdU5abtw==", - "requires": { - "aes-js": "^3.1.2", - "bn.js": "=4.11.9", - "bs58": "=4.0.1", - "clone-deep": "^4.0.1", - "elliptic": "6.5.4", - "hash.js": "^1.1.7", - "inherits": "2.0.3", - "unorm": "1.4.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -6083,16 +5887,6 @@ "wrap-ansi": "^7.0.0" } }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -6257,32 +6051,6 @@ "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==", "dev": true }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - } - } - }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -6953,15 +6721,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -6972,16 +6731,6 @@ "type-fest": "^0.8.0" } }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -7123,14 +6872,6 @@ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -7165,11 +6906,6 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, "istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", @@ -7409,11 +7145,6 @@ "graceful-fs": "^4.1.6" } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, "klaw-sync": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", @@ -7560,16 +7291,6 @@ "picomatch": "^2.3.1" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8345,7 +8066,8 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true }, "semver": { "version": "5.7.2", @@ -8358,14 +8080,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "requires": { - "kind-of": "^6.0.2" - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -8647,9 +8361,9 @@ } }, "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true }, "universalify": { @@ -8657,11 +8371,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, - "unorm": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz", - "integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA=" - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 9f0c5ab3..b693e037 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "td": "mocha", "pretest": "ts-node test/fixture/autoCompile.ts", "prepare": "husky install && npm run compile", - "postinstall": "node ./postinstall.js", "publishcheck": "sh ./publishcheck.sh", "prepublishOnly": "npm run testlint && npm run compile", "getBinary": "node ./util/getBinary.js", @@ -58,12 +57,13 @@ "nyc": "^15.1.0", "ts-loader": "^6.2.1", "ts-node": "^8.6.2", - "typescript": "^4.9.5" + "typescript": "^5.4.3" }, "dependencies": { + "@bsv/sdk": "^1.0.13", "@discoveryjs/json-ext": "^0.5.7", "@jridgewell/sourcemap-codec": "^1.4.15", - "bsv": "^1.5.6", + "chalk": "2.4.2", "compare-versions": "^3.6.0", "find-node-modules": "^2.1.3", "get-proxy-settings": "^0.1.13", @@ -73,7 +73,6 @@ "node-fetch": "^3.0.0", "patch-package": "^6.4.7", "rimraf": "^3.0.2", - "yargs": "^17.6.2", - "chalk": "2.4.2" + "yargs": "^17.6.2" } -} \ No newline at end of file +} diff --git a/src/abi.ts b/src/abi.ts index f9dfbc6d..773039b6 100644 --- a/src/abi.ts +++ b/src/abi.ts @@ -1,4 +1,3 @@ -import { LockingScript, OP, Script, UnlockingScript, Utils } from '@bsv/sdk'; import { bin2num } from './builtins'; import { ABIEntity, ABIEntityType } from './compilerWrapper'; import { AbstractContract, AsmVarValues, TxContext, VerifyResult } from './contract'; @@ -9,7 +8,8 @@ import { toScriptHex } from './serializer'; import Stateful from './stateful'; import { flatternArg } from './typeCheck'; import { asm2int, buildContractCode, int2Asm } from './utils'; -import ScriptChunk from '@bsv/sdk/dist/types/src/script/ScriptChunk'; + +import { Chain, UnlockingScript, LockingScript, Script, ScriptChunk } from './chain'; export type FileUri = string; @@ -221,7 +221,7 @@ export class ABICoder { } encodeConstructorCallFromRawHex(contract: AbstractContract, hexTemplate: string, raw: string): FunctionCall { - const script = LockingScript.fromHex(raw); + const script = Chain.getFactory().LockingScript.fromHex(raw); const constructorABI = this.abi.filter(entity => entity.type === ABIEntityType.CONSTRUCTOR)[0]; const cParams = constructorABI?.params || []; @@ -251,7 +251,7 @@ export class ABICoder { const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (Utils.toHex(chunk.data) != data) { + if (Chain.getFactory().Utils.toHex(chunk.data) != data) { throw err; } offset = offset + chunk.data.length * 2; @@ -276,7 +276,7 @@ export class ABICoder { const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (Utils.toHex(chunk.data) != data) { + if (Chain.getFactory().Utils.toHex(chunk.data) != data) { throw err; } offset = offset + chunk.data.length * 2; @@ -300,7 +300,7 @@ export class ABICoder { const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (Utils.toHex(chunk.data) != data) { + if (Chain.getFactory().Utils.toHex(chunk.data) != data) { throw err; } offset = offset + chunk.data.length * 2; @@ -324,7 +324,7 @@ export class ABICoder { const data = hexTemplate.substring(offset, offset + chunk.data.length * 2); - if (Utils.toHex(chunk.data) != data) { + if (Chain.getFactory().Utils.toHex(chunk.data) != data) { throw err; } @@ -354,19 +354,19 @@ export class ABICoder { } function saveTemplateVariableValue(name: string, chunk: ScriptChunk) { - const bw = new Utils.Writer(); + const bw = Chain.getFactory().Writer.from(); bw.writeUInt8(chunk.op); if (chunk.op > 0) { - if (chunk.op < OP.OP_PUSHDATA1) { + if (chunk.op < Chain.getFactory().OP.OP_PUSHDATA1) { bw.write(chunk.data); - } else if (chunk.op === OP.OP_PUSHDATA1) { + } else if (chunk.op === Chain.getFactory().OP.OP_PUSHDATA1) { bw.writeUInt8(chunk.data.length); bw.write(chunk.data); - } else if (chunk.op === OP.OP_PUSHDATA2) { + } else if (chunk.op === Chain.getFactory().OP.OP_PUSHDATA2) { bw.writeUInt16LE(chunk.data.length); bw.write(chunk.data); - } else if (chunk.op === OP.OP_PUSHDATA4) { + } else if (chunk.op === Chain.getFactory().OP.OP_PUSHDATA4) { bw.writeUInt32LE(chunk.data.length); bw.write(chunk.data); } @@ -375,9 +375,9 @@ export class ABICoder { if (name.startsWith(`<${contract.contractName}.`)) { //inline asm - contract.hexTemplateInlineASM.set(name, Utils.toHex(bw.toArray())); + contract.hexTemplateInlineASM.set(name, Chain.getFactory().Utils.toHex(bw.toArray())); } else { - contract.hexTemplateArgs.set(name, Utils.toHex(bw.toArray())); + contract.hexTemplateArgs.set(name, Chain.getFactory().Utils.toHex(bw.toArray())); } } @@ -393,7 +393,7 @@ export class ABICoder { if (offset >= hexTemplate.length) { - const b = new Script(script.chunks.slice(index + 1)); + const b = Chain.getFactory().LockingScript.from(script.chunks.slice(index + 1)); dataPartInHex = b.toHex(); codePartEndIndex = index; @@ -514,7 +514,7 @@ export class ABICoder { return new FunctionCall('constructor', { contract, - lockingScript: codePartEndIndex > -1 ? new Script(script.chunks.slice(0, codePartEndIndex)) : script, + lockingScript: codePartEndIndex > -1 ? Chain.getFactory().LockingScript.from(script.chunks.slice(0, codePartEndIndex)) : script, args: ctorArgs }); @@ -538,10 +538,10 @@ export class ABICoder { if (this.abi.length > 2 && entity.index !== undefined) { // selector when there are multiple public functions const pubFuncIndex = entity.index; - hex += `${UnlockingScript.fromASM(int2Asm(pubFuncIndex.toString())).toHex()}`; + hex += `${Chain.getFactory().UnlockingScript.fromASM(int2Asm(pubFuncIndex.toString())).toHex()}`; } return new FunctionCall(name, { - contract, unlockingScript: UnlockingScript.fromHex(hex), args: entity.params.map((param, index) => ({ + contract, unlockingScript: Chain.getFactory().UnlockingScript.fromHex(hex), args: entity.params.map((param, index) => ({ name: param.name, type: param.type, value: args_[index] @@ -574,7 +574,7 @@ export class ABICoder { */ parseCallData(hex: string): CallData { - const unlockingScript = UnlockingScript.fromHex(hex); + const unlockingScript = Chain.getFactory().UnlockingScript.fromHex(hex); const usASM = unlockingScript.toASM(); @@ -620,7 +620,7 @@ export class ABICoder { dummyArgs.forEach((farg: Argument, index: number) => { - hexTemplateArgs.set(`<${farg.name}>`, UnlockingScript.fromASM(asmOpcodes[index]).toHex()); + hexTemplateArgs.set(`<${farg.name}>`, Chain.getFactory().UnlockingScript.fromASM(asmOpcodes[index]).toHex()); }); diff --git a/src/builtins.ts b/src/builtins.ts index 8385648b..9780f551 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -1,13 +1,12 @@ -import { BigNumber, Hash, Script, Utils } from '@bsv/sdk'; import { Bytes, Int, Ripemd160 } from '.'; +import { Chain, LockingScript } from './chain'; /** * bigint can be converted to string with pack * @category Bytes Operations */ export function pack(n: bigint): Bytes { - const num = new BigNumber(n.toString().replace(/n/, '')); - return Utils.toHex(num.toSm('little')) + return num2bin(n); } /** @@ -15,7 +14,7 @@ export function pack(n: bigint): Bytes { * @category Bytes Operations */ export function unpack(a: Bytes): bigint { - return BigInt(bin2num(a)); + return bin2num(a); } @@ -24,56 +23,20 @@ export function unpack(a: Bytes): bigint { // Throws if the number cannot be accommodated // Often used to append numbers to OP_RETURN, which are read in contracts // Support Bigint -export function num2bin(n: bigint, dataLen: number): string { - const num = new BigNumber(n.toString().replace(/n/, '')); - if (num.eqn(0)) { - return '00'.repeat(dataLen); - } - const s = Utils.toHex(num.toSm('little')) - - 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 (num.isNeg()) { - // 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; +export function num2bin(n: bigint, dataLen?: number): string { + const bin = Chain.getFactory().Utils.num2bin(n, dataLen); + return toHex(bin); } //Support Bigint export function bin2num(hex: string): bigint { - const lastByte = hex.substring(hex.length - 2); - const rest = hex.substring(0, hex.length - 2); - const m = parseInt(lastByte, 16); - const n = m & 0x7F; - let nHex = n.toString(16); - if (nHex.length < 2) { - nHex = '0' + nHex; - } - //Support negative number - let bn = BigNumber.fromHex(rest + nHex, 'little'); - if (m >> 7) { - bn = bn.neg(); - } - return BigInt(bn.toString()); + const bin = Chain.getFactory().Utils.toArray(hex); + return Chain.getFactory().Utils.bin2num(bin); } +export function toHex(msg: number[]): string { + return Chain.getFactory().Utils.toHex(msg); +} export function and(a: Int, b: Int): Int { const size1 = pack(a).length / 2; @@ -177,13 +140,13 @@ export function writeVarint(b: string): string { } -export function buildOpreturnScript(data: string): Script { - return Script.fromASM(['OP_FALSE', 'OP_RETURN', data].join(' ')); +export function buildOpreturnScript(data: string): LockingScript { + return Chain.getFactory().LockingScript.fromASM(['OP_FALSE', 'OP_RETURN', data].join(' ')); } -export function buildPublicKeyHashScript(pubKeyHash: Ripemd160): Script { - return Script.fromASM(['OP_DUP', 'OP_HASH160', pubKeyHash, 'OP_EQUALVERIFY', 'OP_CHECKSIG'].join(' ')); +export function buildPublicKeyHashScript(pubKeyHash: Ripemd160): LockingScript { + return Chain.getFactory().LockingScript.fromASM(['OP_DUP', 'OP_HASH160', pubKeyHash, 'OP_EQUALVERIFY', 'OP_CHECKSIG'].join(' ')); } @@ -192,16 +155,16 @@ export function buildPublicKeyHashScript(pubKeyHash: Ripemd160): Script { // Equivalent to the built-in function `hash160` in scrypt export function hash160(hexstr: string, encoding?: 'hex' | 'utf8'): string { - return Utils.toHex(Hash.hash160(hexstr, encoding || 'hex')); + return toHex(Chain.getFactory().Hash.hash160(hexstr, encoding || 'hex')); } // Equivalent to the built-in function `sha256` in scrypt export function sha256(hexstr: string, encoding?: 'hex' | 'utf8'): string { - return Utils.toHex(Hash.sha256(hexstr, encoding || 'hex')); + return toHex(Chain.getFactory().Hash.sha256(hexstr, encoding || 'hex')); } // Equivalent to the built-in function `hash256` in scrypt export function hash256(hexstr: string, encoding?: 'hex' | 'utf8'): string { - return Utils.toHex(Hash.hash256(hexstr, encoding || 'hex')); + return toHex(Chain.getFactory().Hash.hash256(hexstr, encoding || 'hex')); } \ No newline at end of file diff --git a/src/chain/base/factory.ts b/src/chain/base/factory.ts new file mode 100644 index 00000000..eaa76e4f --- /dev/null +++ b/src/chain/base/factory.ts @@ -0,0 +1,116 @@ +import { ScriptChunk } from "./script/Script"; + +import { UnlockingScript } from "./script/UnlockingScript"; +import { LockingScript } from "./script/LockingScript"; +import { Transaction } from "./transaction/Transaction"; +import { PrivateKey } from "./primitives/PrivateKey"; +import { PublicKey } from "./primitives/PublicKey"; +import { TransactionInput } from "./transaction/TransactionInput"; +import { TransactionOutput } from "./transaction/TransactionOutput"; +import { Reader } from "./primitives/Reader"; +import { Writer } from "./primitives/Writer"; +import { IOP } from "./iop"; + + +interface ITransaction { + fromHex: (hex: string) => Transaction + from: (version?: number, + inputs?: TransactionInput[], + outputs?: TransactionOutput[], + lockTime?: number, + metadata?: Record) => Transaction; + fromBinary: (bin: number[]) => Transaction +} + + +interface IUnlockingScript { + fromHex: (hex: string) => UnlockingScript, + fromASM: (asm: string) => UnlockingScript, + fromBinary: (bin: number[]) => UnlockingScript, + from: (chunks?: ScriptChunk[]) => UnlockingScript, +} + +interface ILockingScript { + fromHex: (hex: string) => LockingScript, + fromASM: (asm: string) => LockingScript, + fromBinary: (bin: number[]) => LockingScript, + from: (chunks?: ScriptChunk[]) => LockingScript, +} + +interface IPrivateKey { + fromRandom: () => PrivateKey, + fromString: (str: string, base: number | 'hex') => PrivateKey, + fromWif: (wif: string, prefixLength?: number) => PrivateKey, + + from: (number: bigint | number | string | number[], + base?: number | 'be' | 'le' | 'hex', + endian?: 'be' | 'le', + modN?: 'apply' | 'nocheck' | 'error') => PrivateKey, +} + + +interface IPublicKey { + fromPrivateKey: (key: PrivateKey) => PublicKey, + fromString: (str: string) => PublicKey, + from: (x: bigint | number | number[] | string, + y: bigint | number | number[] | string) => PublicKey, +} + + +interface IHash { + ripemd160: (msg: number[] | string, enc?: 'hex' | 'utf8') => number[]; + sha1: (msg: number[] | string, enc?: 'hex' | 'utf8') => number[]; + sha256: (msg: number[] | string, enc?: 'hex' | 'utf8') => number[]; + sha512: (msg: number[] | string, enc?: 'hex' | 'utf8') => number[]; + hash256: (msg: number[] | string, enc?: 'hex' | 'utf8') => number[]; + hash160: (msg: number[] | string, enc?: 'hex' | 'utf8') => number[]; + sha256hmac: (key: number[] | string, msg: number[] | string, enc?: 'hex') => number[]; + sha512hmac: (key: number[] | string, msg: number[] | string, enc?: 'hex') => number[]; +} + +interface IUtils { + + toHex: (msg: number[]) => string; + toArray: (msg: any, enc?: 'hex' | 'utf8' | 'base64') => any[]; + toUTF8: (arr: number[]) => string; + encode: (arr: number[], enc?: 'hex' | 'utf8') => string | number[]; + toBase64: (byteArray: number[]) => string; + + fromBase58: (str: string) => number[]; + toBase58: (bin: number[]) => string; + toBase58Check: (bin: number[], prefix?: number[]) => string; + + fromBase58Check: (str: string, enc?: 'hex', prefixLength?: number) => { + prefix: string | number[], + data: string | number[], + } + + getPreimage(tx: Transaction, subscript: LockingScript, inputAmount: number, inputIndex?: number, sighashType?: number): number[]; + + + num2bin(n: bigint, dataLen?: number): number[]; + bin2num(bin: number[]): bigint; +} + +interface IReader { + from: (bin?: number[], pos?: number) => Reader; +} + +interface IWriter { + from: (bufs?: number[][]) => Writer; +} + + +export interface Factory { + Transaction: ITransaction, + UnlockingScript: IUnlockingScript, + LockingScript: ILockingScript, + PrivateKey: IPrivateKey, + PublicKey: IPublicKey, + Hash: IHash, + Utils: IUtils, + Reader: IReader, + Writer: IWriter, + OP: IOP + +} \ No newline at end of file diff --git a/src/chain/base/index.ts b/src/chain/base/index.ts new file mode 100644 index 00000000..9e318b1a --- /dev/null +++ b/src/chain/base/index.ts @@ -0,0 +1,19 @@ +export * from './factory' +export * from './primitives/PrivateKey' +export * from './primitives/PublicKey' +export * from './primitives/BigNumber' +export * from './primitives/Point' +export * from './primitives/Reader' +export * from './primitives/Writer' +export * from './primitives/Signature' +export * from './script/Script' +export * from './script/LockingScript' +export * from './script/OP' +export * from './script/UnlockingScript' +export * from './transaction/Transaction' +export * from './transaction/Broadcaster' +export * from './transaction/ChainTracker' +export * from './transaction/FeeModel' +export * from './transaction/MerklePath' +export * from './transaction/TransactionInput' +export * from './transaction/TransactionOutput' \ No newline at end of file diff --git a/src/chain/base/iop.ts b/src/chain/base/iop.ts new file mode 100644 index 00000000..d7f86090 --- /dev/null +++ b/src/chain/base/iop.ts @@ -0,0 +1,208 @@ +export interface IOP { + OP_FALSE: number, + OP_0: number, + OP_PUSHDATA1: number, + OP_PUSHDATA2: number, + OP_PUSHDATA4: number, + OP_1NEGATE: number, + OP_RESERVED: number, + OP_TRUE: number, + OP_1: number, + OP_2: number, + OP_3: number, + OP_4: number, + OP_5: number, + OP_6: number, + OP_7: number, + OP_8: number, + OP_9: number, + OP_10: number, + OP_11: number, + OP_12: number, + OP_13: number, + OP_14: number, + OP_15: number, + OP_16: number, + + // control + OP_NOP: number, + OP_VER: number, + OP_IF: number, + OP_NOTIF: number, + OP_VERIF: number, + OP_VERNOTIF: number, + OP_ELSE: number, + OP_ENDIF: number, + OP_VERIFY: number, + OP_RETURN: number, + + // stack ops + OP_TOALTSTACK: number, + OP_FROMALTSTACK: number, + OP_2DROP: number, + OP_2DUP: number, + OP_3DUP: number, + OP_2OVER: number, + OP_2ROT: number, + OP_2SWAP: number, + OP_IFDUP: number, + OP_DEPTH: number, + OP_DROP: number, + OP_DUP: number, + OP_NIP: number, + OP_OVER: number, + OP_PICK: number, + OP_ROLL: number, + OP_ROT: number, + OP_SWAP: number, + OP_TUCK: number, + + // data manipulation ops + OP_CAT: number, + OP_SUBSTR: number, // Replaced in BSV + OP_SPLIT: number, + OP_LEFT: number, // Replaced in BSV + OP_NUM2BIN: number, + OP_RIGHT: number, // Replaced in BSV + OP_BIN2NUM: number, + OP_SIZE: number, + + // bit logic + OP_INVERT: number, + OP_AND: number, + OP_OR: number, + OP_XOR: number, + OP_EQUAL: number, + OP_EQUALVERIFY: number, + OP_RESERVED1: number, + OP_RESERVED2: number, + + // numeric + OP_1ADD: number, + OP_1SUB: number, + OP_2MUL: number, + OP_2DIV: number, + OP_NEGATE: number, + OP_ABS: number, + OP_NOT: number, + OP_0NOTEQUAL: number, + + OP_ADD: number, + OP_SUB: number, + OP_MUL: number, + OP_DIV: number, + OP_MOD: number, + OP_LSHIFT: number, + OP_RSHIFT: number, + + OP_BOOLAND: number, + OP_BOOLOR: number, + OP_NUMEQUAL: number, + OP_NUMEQUALVERIFY: number, + OP_NUMNOTEQUAL: number, + OP_LESSTHAN: number, + OP_GREATERTHAN: number, + OP_LESSTHANOREQUAL: number, + OP_GREATERTHANOREQUAL: number, + OP_MIN: number, + OP_MAX: number, + + OP_WITHIN: number, + + // crypto + OP_RIPEMD160: number, + OP_SHA1: number, + OP_SHA256: number, + OP_HASH160: number, + OP_HASH256: number, + OP_CODESEPARATOR: number, + OP_CHECKSIG: number, + OP_CHECKSIGVERIFY: number, + OP_CHECKMULTISIG: number, + OP_CHECKMULTISIGVERIFY: number, + + // expansion + OP_NOP1: number, + OP_NOP2: number, + OP_NOP3: number, + OP_NOP4: number, + OP_NOP5: number, + OP_NOP6: number, + OP_NOP7: number, + OP_NOP8: number, + OP_NOP9: number, + OP_NOP10: number, + OP_NOP11: number, + OP_NOP12: number, + OP_NOP13: number, + OP_NOP14: number, + OP_NOP15: number, + OP_NOP16: number, + OP_NOP17: number, + OP_NOP18: number, + OP_NOP19: number, + OP_NOP20: number, + OP_NOP21: number, + OP_NOP22: number, + OP_NOP23: number, + OP_NOP24: number, + OP_NOP25: number, + OP_NOP26: number, + OP_NOP27: number, + OP_NOP28: number, + OP_NOP29: number, + OP_NOP30: number, + OP_NOP31: number, + OP_NOP32: number, + OP_NOP33: number, + OP_NOP34: number, + OP_NOP35: number, + OP_NOP36: number, + OP_NOP37: number, + OP_NOP38: number, + OP_NOP39: number, + OP_NOP40: number, + OP_NOP41: number, + OP_NOP42: number, + OP_NOP43: number, + OP_NOP44: number, + OP_NOP45: number, + OP_NOP46: number, + OP_NOP47: number, + OP_NOP48: number, + OP_NOP49: number, + OP_NOP50: number, + OP_NOP51: number, + OP_NOP52: number, + OP_NOP53: number, + OP_NOP54: number, + OP_NOP55: number, + OP_NOP56: number, + OP_NOP57: number, + OP_NOP58: number, + OP_NOP59: number, + OP_NOP60: number, + OP_NOP61: number, + OP_NOP62: number, + OP_NOP63: number, + OP_NOP64: number, + OP_NOP65: number, + OP_NOP66: number, + OP_NOP67: number, + OP_NOP68: number, + OP_NOP69: number, + OP_NOP70: number, + OP_NOP71: number, + OP_NOP72: number, + OP_NOP73: number, + OP_NOP77: number, + + // template matching params + OP_SMALLDATA: number, + OP_SMALLINTEGER: number, + OP_PUBKEYS: number, + OP_PUBKEYHASH: number, + OP_PUBKEY: number, + + OP_INVALIDOPCODE: number, +} \ No newline at end of file diff --git a/src/chain/base/primitives/BasePoint.ts b/src/chain/base/primitives/BasePoint.ts new file mode 100644 index 00000000..9aec6203 --- /dev/null +++ b/src/chain/base/primitives/BasePoint.ts @@ -0,0 +1,8 @@ +export interface BasePoint { + type: 'affine' | 'jacobian' + precomputed: { + doubles: { step: number, points: any[] } | undefined + naf: { wnd: any, points: any[] } | undefined + beta: BasePoint | null | undefined + } | null +} \ No newline at end of file diff --git a/src/chain/base/primitives/BigNumber.ts b/src/chain/base/primitives/BigNumber.ts new file mode 100644 index 00000000..91131cfd --- /dev/null +++ b/src/chain/base/primitives/BigNumber.ts @@ -0,0 +1,67 @@ +export interface BigNumber { + + copy(dest: BigNumber): void; + clone(): BigNumber; + expand(size): BigNumber; + strip(): BigNumber; + normSign(): BigNumber; + inspect(): string; + toString(base?: number | 'hex', padding?: number): string; + toNumber(): number; + toJSON(): string; + toArray(endian?: 'le' | 'be', length?: number): number[]; + bitLength(): number; + toBitArray(): Array<0 | 1>; + zeroBits(): number; + byteLength(): number; + toTwos(width: number): BigNumber; + fromTwos(width: number): BigNumber; + isNeg(): boolean; + neg(): BigNumber; + or(num: BigNumber): BigNumber; + xor(num: BigNumber): BigNumber; + uxor(num: BigNumber): BigNumber; + notn(width: number): BigNumber; + + setn(bit: number, val: 0 | 1 | true | false): BigNumber; + add(num: BigNumber): BigNumber + sub(num: BigNumber): BigNumber; + mulTo(num: BigNumber, out: BigNumber): BigNumber; + mul(num: BigNumber): BigNumber; + muln(num: number): BigNumber; + sqr(): BigNumber; + pow(num: BigNumber): BigNumber; + + addn(num: number): BigNumber; + subn(num: number): BigNumber; + abs(): BigNumber; + div(num: BigNumber): BigNumber; + mod(num: BigNumber): BigNumber; + umod(num: BigNumber): BigNumber; + divRound(num: BigNumber): BigNumber; + modrn(num: number): number; + divn(num: number): BigNumber; + isEven(): boolean; + isOdd(): boolean; + andln(num: number): number; + bincn(bit: number): BigNumber; + isZero(): boolean; + cmpn(num: number): 1 | 0 | -1; + cmp(num: BigNumber): 1 | 0 | -1; + ucmp(num: BigNumber): 1 | 0 | -1; + gtn(num: number): boolean; + gt(num: BigNumber): boolean; + gten(num: number): boolean; + gte(num: BigNumber): boolean; + ltn(num: number): boolean; + lt(num: BigNumber): boolean; + lten(num: number): boolean; + lte(num: BigNumber): boolean; + eqn(num: number): boolean; + eq(num: BigNumber): boolean; + + toHex(length?: number): string; + toSm(endian?: 'big' | 'little'): number[]; + toBits(): number; + toScriptNum(): number[]; +} \ No newline at end of file diff --git a/src/chain/base/primitives/Point.ts b/src/chain/base/primitives/Point.ts new file mode 100644 index 00000000..91567ffd --- /dev/null +++ b/src/chain/base/primitives/Point.ts @@ -0,0 +1,23 @@ +import { BasePoint } from "./BasePoint" +import { BigNumber } from "./BigNumber" +export interface Point extends BasePoint { + x: BigNumber | null + y: BigNumber | null + inf: boolean + + validate(): boolean; + encode(compact?: boolean, enc?: 'hex'): number[] | string; + toString(): string; + toJSON(): [BigNumber | null, BigNumber | null, { doubles: { step: any, points: any[] } | undefined, naf: { wnd: any, points: any[] } | undefined }?]; + inspect(): string; + isInfinity(): boolean; + add(p: Point): Point; + dbl(): Point; + getX(): BigNumber; + getY(): BigNumber; + mul(k: BigNumber | number | number[] | string): Point; + mulAdd(k1: BigNumber, p2: Point, k2: BigNumber): Point; + eq(p: Point): boolean; + neg(_precompute?: boolean): Point; + dblp(k: number): Point; +} \ No newline at end of file diff --git a/src/chain/base/primitives/PrivateKey.ts b/src/chain/base/primitives/PrivateKey.ts new file mode 100644 index 00000000..a3908d5b --- /dev/null +++ b/src/chain/base/primitives/PrivateKey.ts @@ -0,0 +1,19 @@ +import { Signature } from "./Signature"; +import { PublicKey } from "./PublicKey"; +import { Point } from "./Point"; +import { BigNumber } from "./BigNumber"; + + +export interface PrivateKey extends BigNumber { + + isValid(): boolean; + toPublicKey(): PublicKey; + toWif(prefix?: number[]): string; + + toAddress(prefix?: number[]): string; + // eslint-disable-next-line @typescript-eslint/ban-types + sign(msg: number[] | string, enc?: 'hex' | 'utf8', forceLowS?: boolean, customK?: Function | bigint): Signature + verify(msg: number[] | string, sig: Signature, enc?: 'hex'): boolean + deriveSharedSecret(key: PublicKey): Point; + deriveChild(publicKey: PublicKey, invoiceNumber: string): PrivateKey; +} \ No newline at end of file diff --git a/src/chain/base/primitives/PublicKey.ts b/src/chain/base/primitives/PublicKey.ts new file mode 100644 index 00000000..bb46871c --- /dev/null +++ b/src/chain/base/primitives/PublicKey.ts @@ -0,0 +1,12 @@ +import { Point } from "./Point"; +import { PrivateKey } from "./PrivateKey"; +import { Signature } from "./Signature"; + + +export interface PublicKey extends Point { + verify(msg: number[] | string, sig: Signature, enc?: 'hex' | 'utf8'): boolean; + toDER(): string; + toHash(enc?: 'hex'): number[] | string; + toAddress(prefix: number[]): string; + deriveChild(privateKey: PrivateKey, invoiceNumber: string): PublicKey; +} \ No newline at end of file diff --git a/src/chain/base/primitives/Reader.ts b/src/chain/base/primitives/Reader.ts new file mode 100644 index 00000000..05cc0d8c --- /dev/null +++ b/src/chain/base/primitives/Reader.ts @@ -0,0 +1,26 @@ +export interface Reader { + bin: number[]; + pos: number; + eof(): boolean; + read(len?: number): number[]; + readReverse(len?: number): number[] + readUInt8(): number; + readInt8(): number; + readUInt16BE(): number; + readInt16BE(): number; + readUInt16LE(): number; + readInt16LE(): number; + readUInt32BE(): number; + readInt32BE(): number; + readUInt32LE(): number; + readInt32LE(): number; + readUInt64BEBn(): bigint; + + readUInt64LEBn(): bigint; + + readVarIntNum(): number; + + readVarInt(): number[]; + + readVarIntBn(): bigint; +} \ No newline at end of file diff --git a/src/chain/base/primitives/Signature.ts b/src/chain/base/primitives/Signature.ts new file mode 100644 index 00000000..b68726bc --- /dev/null +++ b/src/chain/base/primitives/Signature.ts @@ -0,0 +1,10 @@ + +import { PublicKey } from "./PublicKey"; + + +export interface Signature { + verify(msg: number[] | string, key: PublicKey, enc?: 'hex'): boolean; + toString(enc?: 'hex' | 'base64'); + + toDER(enc?: 'hex' | 'base64'): number[] | string; +} \ No newline at end of file diff --git a/src/chain/base/primitives/Writer.ts b/src/chain/base/primitives/Writer.ts new file mode 100644 index 00000000..2c0d6bfc --- /dev/null +++ b/src/chain/base/primitives/Writer.ts @@ -0,0 +1,37 @@ +export interface Writer { + bufs: number[][] + getLength(): number; + toArray(): number[]; + write(buf: number[]): Writer; + writeReverse(buf: number[]): Writer; + writeUInt8(n: number): Writer; + + writeInt8(n: number): Writer; + writeUInt16BE(n: number): Writer; + + writeInt16BE(n: number): Writer; + + writeUInt16LE(n: number): Writer; + + writeInt16LE(n: number): Writer; + + writeUInt32BE(n: number): Writer; + + writeInt32BE(n: number): Writer; + + writeUInt32LE(n: number): Writer; + + writeInt32LE(n: number): Writer; + + writeUInt64BEBn(bn: bigint): Writer; + + writeUInt64LEBn(bn: bigint): Writer; + + writeUInt64LE(n: number): Writer; + + writeVarIntNum(n: number): Writer; + + writeVarIntBn(bn: bigint): Writer; + + +} \ No newline at end of file diff --git a/src/chain/base/script/LockingScript.ts b/src/chain/base/script/LockingScript.ts new file mode 100644 index 00000000..68742260 --- /dev/null +++ b/src/chain/base/script/LockingScript.ts @@ -0,0 +1,5 @@ +import { Script } from "./Script"; + +export interface LockingScript extends Script { + +} \ No newline at end of file diff --git a/src/chain/base/script/OP.ts b/src/chain/base/script/OP.ts new file mode 100644 index 00000000..aa454be2 --- /dev/null +++ b/src/chain/base/script/OP.ts @@ -0,0 +1,217 @@ +/** + * An object mapping opcode names (such as OP_DUP) to their corresponding numbers (such as 0x76), and vice versa. + */ +export const OP = { + // push value + OP_FALSE: 0x00, + OP_0: 0x00, + OP_PUSHDATA1: 0x4c, + OP_PUSHDATA2: 0x4d, + OP_PUSHDATA4: 0x4e, + OP_1NEGATE: 0x4f, + OP_RESERVED: 0x50, + OP_TRUE: 0x51, + OP_1: 0x51, + OP_2: 0x52, + OP_3: 0x53, + OP_4: 0x54, + OP_5: 0x55, + OP_6: 0x56, + OP_7: 0x57, + OP_8: 0x58, + OP_9: 0x59, + OP_10: 0x5a, + OP_11: 0x5b, + OP_12: 0x5c, + OP_13: 0x5d, + OP_14: 0x5e, + OP_15: 0x5f, + OP_16: 0x60, + + // control + OP_NOP: 0x61, + OP_VER: 0x62, + OP_IF: 0x63, + OP_NOTIF: 0x64, + OP_VERIF: 0x65, + OP_VERNOTIF: 0x66, + OP_ELSE: 0x67, + OP_ENDIF: 0x68, + OP_VERIFY: 0x69, + OP_RETURN: 0x6a, + + // stack ops + OP_TOALTSTACK: 0x6b, + OP_FROMALTSTACK: 0x6c, + OP_2DROP: 0x6d, + OP_2DUP: 0x6e, + OP_3DUP: 0x6f, + OP_2OVER: 0x70, + OP_2ROT: 0x71, + OP_2SWAP: 0x72, + OP_IFDUP: 0x73, + OP_DEPTH: 0x74, + OP_DROP: 0x75, + OP_DUP: 0x76, + OP_NIP: 0x77, + OP_OVER: 0x78, + OP_PICK: 0x79, + OP_ROLL: 0x7a, + OP_ROT: 0x7b, + OP_SWAP: 0x7c, + OP_TUCK: 0x7d, + + // data manipulation ops + OP_CAT: 0x7e, + OP_SUBSTR: 0x7f, // Replaced in BSV + OP_SPLIT: 0x7f, + OP_LEFT: 0x80, // Replaced in BSV + OP_NUM2BIN: 0x80, + OP_RIGHT: 0x81, // Replaced in BSV + OP_BIN2NUM: 0x81, + OP_SIZE: 0x82, + + // bit logic + OP_INVERT: 0x83, + OP_AND: 0x84, + OP_OR: 0x85, + OP_XOR: 0x86, + OP_EQUAL: 0x87, + OP_EQUALVERIFY: 0x88, + OP_RESERVED1: 0x89, + OP_RESERVED2: 0x8a, + + // numeric + OP_1ADD: 0x8b, + OP_1SUB: 0x8c, + OP_2MUL: 0x8d, + OP_2DIV: 0x8e, + OP_NEGATE: 0x8f, + OP_ABS: 0x90, + OP_NOT: 0x91, + OP_0NOTEQUAL: 0x92, + + OP_ADD: 0x93, + OP_SUB: 0x94, + OP_MUL: 0x95, + OP_DIV: 0x96, + OP_MOD: 0x97, + OP_LSHIFT: 0x98, + OP_RSHIFT: 0x99, + + OP_BOOLAND: 0x9a, + OP_BOOLOR: 0x9b, + OP_NUMEQUAL: 0x9c, + OP_NUMEQUALVERIFY: 0x9d, + OP_NUMNOTEQUAL: 0x9e, + OP_LESSTHAN: 0x9f, + OP_GREATERTHAN: 0xa0, + OP_LESSTHANOREQUAL: 0xa1, + OP_GREATERTHANOREQUAL: 0xa2, + OP_MIN: 0xa3, + OP_MAX: 0xa4, + + OP_WITHIN: 0xa5, + + // crypto + OP_RIPEMD160: 0xa6, + OP_SHA1: 0xa7, + OP_SHA256: 0xa8, + OP_HASH160: 0xa9, + OP_HASH256: 0xaa, + OP_CODESEPARATOR: 0xab, + OP_CHECKSIG: 0xac, + OP_CHECKSIGVERIFY: 0xad, + OP_CHECKMULTISIG: 0xae, + OP_CHECKMULTISIGVERIFY: 0xaf, + + // expansion + OP_NOP1: 0xb0, + OP_NOP2: 0xb1, + OP_NOP3: 0xb2, + OP_NOP4: 0xb3, + OP_NOP5: 0xb4, + OP_NOP6: 0xb5, + OP_NOP7: 0xb6, + OP_NOP8: 0xb7, + OP_NOP9: 0xb8, + OP_NOP10: 0xb9, + OP_NOP11: 0xba, + OP_NOP12: 0xbb, + OP_NOP13: 0xbc, + OP_NOP14: 0xbd, + OP_NOP15: 0xbe, + OP_NOP16: 0xbf, + OP_NOP17: 0xc0, + OP_NOP18: 0xc1, + OP_NOP19: 0xc2, + OP_NOP20: 0xc3, + OP_NOP21: 0xc4, + OP_NOP22: 0xc5, + OP_NOP23: 0xc6, + OP_NOP24: 0xc7, + OP_NOP25: 0xc8, + OP_NOP26: 0xc9, + OP_NOP27: 0xca, + OP_NOP28: 0xcb, + OP_NOP29: 0xcc, + OP_NOP30: 0xcd, + OP_NOP31: 0xce, + OP_NOP32: 0xcf, + OP_NOP33: 0xd0, + OP_NOP34: 0xd1, + OP_NOP35: 0xd2, + OP_NOP36: 0xd3, + OP_NOP37: 0xd4, + OP_NOP38: 0xd5, + OP_NOP39: 0xd6, + OP_NOP40: 0xd7, + OP_NOP41: 0xd8, + OP_NOP42: 0xd9, + OP_NOP43: 0xda, + OP_NOP44: 0xdb, + OP_NOP45: 0xdc, + OP_NOP46: 0xdd, + OP_NOP47: 0xde, + OP_NOP48: 0xdf, + OP_NOP49: 0xe0, + OP_NOP50: 0xe1, + OP_NOP51: 0xe2, + OP_NOP52: 0xe3, + OP_NOP53: 0xe4, + OP_NOP54: 0xe5, + OP_NOP55: 0xe6, + OP_NOP56: 0xe7, + OP_NOP57: 0xe8, + OP_NOP58: 0xe9, + OP_NOP59: 0xea, + OP_NOP60: 0xeb, + OP_NOP61: 0xec, + OP_NOP62: 0xed, + OP_NOP63: 0xee, + OP_NOP64: 0xef, + OP_NOP65: 0xf0, + OP_NOP66: 0xf1, + OP_NOP67: 0xf2, + OP_NOP68: 0xf3, + OP_NOP69: 0xf4, + OP_NOP70: 0xf5, + OP_NOP71: 0xf6, + OP_NOP72: 0xf7, + OP_NOP73: 0xf8, + OP_NOP77: 0xfc, + + // template matching params + OP_SMALLDATA: 0xf9, + OP_SMALLINTEGER: 0xfa, + OP_PUBKEYS: 0xfb, + OP_PUBKEYHASH: 0xfd, + OP_PUBKEY: 0xfe, + + OP_INVALIDOPCODE: 0xff +} + +for (const name in OP) { + OP[OP[name]] = name + OP[String(OP[name])] = name +} \ No newline at end of file diff --git a/src/chain/base/script/Script.ts b/src/chain/base/script/Script.ts new file mode 100644 index 00000000..a3ed93cf --- /dev/null +++ b/src/chain/base/script/Script.ts @@ -0,0 +1,40 @@ + +export interface ScriptChunk { + op: number + data?: number[] +} + + +export interface Script { + + chunks: ScriptChunk[]; + + toASM(): string; + + toHex(): string; + + toBinary(): number[]; + + writeScript(script: Script): this; + + writeOpCode(op: number): this; + + writeBn(bn: bigint): this; + + writeBin(bin: number[]): this; + + writeNumber(num: number): this; + + removeCodeseparators(): this; + + findAndDelete(script: Script): this; + + isPushOnly(): boolean; + + isLockingScript(): boolean; + + isUnlockingScript(): boolean; + + setChunkOpCode(): Script; + +} \ No newline at end of file diff --git a/src/chain/base/script/UnlockingScript.ts b/src/chain/base/script/UnlockingScript.ts new file mode 100644 index 00000000..21348f25 --- /dev/null +++ b/src/chain/base/script/UnlockingScript.ts @@ -0,0 +1,5 @@ +import { Script } from "./Script"; + +export interface UnlockingScript extends Script { + +} \ No newline at end of file diff --git a/src/chain/base/transaction/Broadcaster.ts b/src/chain/base/transaction/Broadcaster.ts new file mode 100644 index 00000000..d8299f38 --- /dev/null +++ b/src/chain/base/transaction/Broadcaster.ts @@ -0,0 +1,42 @@ +import Transaction from './Transaction.js' + +/** + * Defines the structure of a successful broadcast response. + * + * @interface + * @property {string} status - The status of the response, indicating success. + * @property {string} txid - The transaction ID of the broadcasted transaction. + * @property {string} message - A human-readable success message. + */ +export interface BroadcastResponse { + status: 'success' + txid: string + message: string +} + +/** + * Defines the structure of a failed broadcast response. + * + * @interface + * @property {string} status - The status of the response, indicating an error. + * @property {string} code - A machine-readable error code representing the type of error encountered. + * @property {string} description - A detailed description of the error. + */ +export interface BroadcastFailure { + status: 'error' + code: string + description: string +} + +/** + * Represents the interface for a transaction broadcaster. + * This interface defines a standard method for broadcasting transactions. + * + * @interface + * @property {function} broadcast - A function that takes a Transaction object and returns a promise. + * The promise resolves to either a BroadcastResponse or a BroadcastFailure. + */ +export interface Broadcaster { + broadcast: (transaction: Transaction) => + Promise +} diff --git a/src/chain/base/transaction/ChainTracker.ts b/src/chain/base/transaction/ChainTracker.ts new file mode 100644 index 00000000..d60266a4 --- /dev/null +++ b/src/chain/base/transaction/ChainTracker.ts @@ -0,0 +1,22 @@ +/** + * The Chain Tracker is responsible for verifying the validity of a given Merkle root + * for a specific block height within the blockchain. + * + * Chain Trackers ensure the integrity of the blockchain by + * validating new headers against the chain's history. They use accumulated + * proof-of-work and protocol adherence as metrics to assess the legitimacy of blocks. + * + * @interface ChainTracker + * @function isValidRootForHeight - A method to verify the validity of a Merkle root + * for a given block height. + * + * @example + * const chainTracker = { + * isValidRootForHeight: async (root, height) => { + * // Implementation to check if the Merkle root is valid for the specified block height. + * } + * }; + */ +export interface ChainTracker { + isValidRootForHeight: (root: string, height: number) => Promise +} diff --git a/src/chain/base/transaction/FeeModel.ts b/src/chain/base/transaction/FeeModel.ts new file mode 100644 index 00000000..9686bd9e --- /dev/null +++ b/src/chain/base/transaction/FeeModel.ts @@ -0,0 +1,12 @@ +import Transaction from './Transaction.js' + +/** + * Represents the interface for a transaction fee model. + * This interface defines a standard method for computing a fee when given a transaction. + * + * @interface + * @property {function} computeFee - A function that takes a Transaction object and returns a BigNumber representing the number of satoshis the transaction should cost. + */ +export interface FeeModel { + computeFee: (transaction: Transaction) => Promise +} diff --git a/src/chain/base/transaction/MerklePath.ts b/src/chain/base/transaction/MerklePath.ts new file mode 100644 index 00000000..606195f7 --- /dev/null +++ b/src/chain/base/transaction/MerklePath.ts @@ -0,0 +1,17 @@ +import { ChainTracker } from "./ChainTracker"; + +export interface MerklePath { + + blockHeight: number; + path: Array>; + toBinary(): number[]; + toHex(): string; + computeRoot(txid?: string): string; + verify(txid: string, chainTracker: ChainTracker): Promise; + combine(other: MerklePath): void; +} diff --git a/src/chain/base/transaction/Transaction.ts b/src/chain/base/transaction/Transaction.ts new file mode 100644 index 00000000..e487b338 --- /dev/null +++ b/src/chain/base/transaction/Transaction.ts @@ -0,0 +1,34 @@ +import { Broadcaster, BroadcastResponse, BroadcastFailure } from "./Broadcaster"; +import { ChainTracker } from "./ChainTracker"; +import { FeeModel } from "./FeeModel"; +import { TransactionInput } from "./TransactionInput"; +import { TransactionOutput } from "./TransactionOutput"; + + +export interface Transaction { + version: number + inputs: TransactionInput[] + outputs: TransactionOutput[] + lockTime: number + metadata: Record + + addInput(input: TransactionInput): void; + addOutput(output: TransactionOutput): void; + + updateMetadata(metadata: Record): void; + + fee(model?: FeeModel, changeDistribution?: 'equal' | 'random'): Promise; + sign(): Promise; + + broadcast(broadcaster: Broadcaster): Promise; + + toBinary(): number[]; + toEF(): number[]; + toHexEF(): string; + toHex(): string; + toHexBEEF(): string; + hash(enc?: 'hex'): number[] | string; + id(enc?: 'hex'): number[] | string; + verify(chainTracker: ChainTracker | 'scripts only'): Promise; + toBEEF(): number[]; +} \ No newline at end of file diff --git a/src/chain/base/transaction/TransactionInput.ts b/src/chain/base/transaction/TransactionInput.ts new file mode 100644 index 00000000..6bf8ce7a --- /dev/null +++ b/src/chain/base/transaction/TransactionInput.ts @@ -0,0 +1,14 @@ +import { UnlockingScript } from "../script/UnlockingScript" +import { Transaction } from "./Transaction" + +export interface TransactionInput { + sourceTransaction?: Transaction + sourceTXID?: string + sourceOutputIndex: number + unlockingScript?: UnlockingScript + unlockingScriptTemplate?: { + sign: (tx: Transaction, inputIndex: number) => Promise + estimateLength: (tx: Transaction, inputIndex: number) => Promise + } + sequence: number +} diff --git a/src/chain/base/transaction/TransactionOutput.ts b/src/chain/base/transaction/TransactionOutput.ts new file mode 100644 index 00000000..1bb2d857 --- /dev/null +++ b/src/chain/base/transaction/TransactionOutput.ts @@ -0,0 +1,36 @@ +import { LockingScript } from '../script/LockingScript.js' + +/** + * Represents an output in a Bitcoin transaction. + * This interface defines the structure and components necessary to construct + * a transaction output, which secures owned Bitcoins to be unlocked later. + * + * @interface TransactionOutput + * @property {number} [satoshis] - Optional. The amount of satoshis (the smallest unit of Bitcoin) to be transferred by this output. + * @property {LockingScript} lockingScript - The script that 'locks' the satoshis, + * specifying the conditions under which they can be spent. This script is + * essential for securing the funds and typically contains cryptographic + * puzzles that need to be solved to spend the output. + * @property {boolean} [change] - Optional. A flag that indicates whether this output + * is a change output. If true, it means this output is returning funds back + * to the sender, usually as the 'change' from the transaction inputs. + * + * @example + * // Creating a simple transaction output + * let txOutput = { + * satoshis: 1000, + * lockingScript: LockingScript.fromASM('OP_DUP OP_HASH160 ... OP_EQUALVERIFY OP_CHECKSIG'), + * change: false + * }; + * + * @description + * The TransactionOutput interface defines how bitcoins are to be distributed in a transaction, either to a recipient or + * back to the sender as change. The lockingScript is critical as it determines the conditions + * under which the output can be spent, typically requiring a digital signature matching the + * intended recipient's public key. + */ +export interface TransactionOutput { + satoshis?: number + lockingScript: LockingScript + change?: boolean +} diff --git a/src/chain/bsv/factory.ts b/src/chain/bsv/factory.ts new file mode 100644 index 00000000..35d809f1 --- /dev/null +++ b/src/chain/bsv/factory.ts @@ -0,0 +1,256 @@ + +import { + OP, Factory, Transaction, TransactionOutput, + TransactionInput, PrivateKey, PublicKey, UnlockingScript, + LockingScript, ScriptChunk +} from "../base"; +import * as bsv from "@bsv/sdk"; +import createScriptProxy from "./script/script"; +import createTransactionProxy from "./transaction/transaction"; +import createPrivateKeyProxy from "./primitives/PrivateKey"; +import createPublicKeyProxy from "./primitives/PublicKey"; +import createReaderProxy from "./primitives/Reader"; +import createWriterProxy from "./primitives/Writer"; +import { TARGET } from "./target"; + + + +export class BSVFactory implements Factory { + Reader = { + from: function (bin?: number[], pos?: number) { + return createReaderProxy(new bsv.Utils.Reader(bin, pos)); + } + }; + Writer = { + from: function (bufs?: number[][]) { + return createWriterProxy(new bsv.Utils.Writer(bufs)); + } + }; + Hash = { + ripemd160: function (msg: string | number[], enc?: "hex" | "utf8"): number[] { + return bsv.Hash.ripemd160(msg, enc); + }, + sha1: function (msg: string | number[], enc?: "hex" | "utf8"): number[] { + return bsv.Hash.sha1(msg, enc); + }, + sha256: function (msg: string | number[], enc?: "hex" | "utf8"): number[] { + return bsv.Hash.sha256(msg, enc); + }, + sha512: function (msg: string | number[], enc?: "hex" | "utf8"): number[] { + return bsv.Hash.sha512(msg, enc); + }, + hash256: function (msg: string | number[], enc?: "hex" | "utf8"): number[] { + return bsv.Hash.hash256(msg, enc); + }, + hash160: function (msg: string | number[], enc?: "hex" | "utf8"): number[] { + return bsv.Hash.hash160(msg, enc); + }, + sha256hmac: function (key: string | number[], msg: string | number[], enc?: "hex"): number[] { + return bsv.Hash.sha256hmac(msg, enc); + }, + sha512hmac: function (key: string | number[], msg: string | number[], enc?: "hex"): number[] { + return bsv.Hash.sha512hmac(msg, enc); + } + } + Utils = { + toHex: function (msg: number[]): string { + return bsv.Utils.toHex(msg); + }, + toArray: function (msg: any, enc?: "hex" | "utf8" | "base64"): any[] { + return bsv.Utils.toArray(msg, enc); + }, + toUTF8: function (arr: number[]): string { + return bsv.Utils.toUTF8(arr); + }, + encode: function (arr: number[], enc?: "hex" | "utf8"): string | number[] { + return bsv.Utils.encode(arr, enc); + }, + toBase64: function (byteArray: number[]): string { + return bsv.Utils.toBase64(byteArray); + }, + fromBase58: function (str: string): number[] { + return bsv.Utils.fromBase58(str); + }, + toBase58: function (bin: number[]): string { + return bsv.Utils.toBase58(bin); + }, + toBase58Check: function (bin: number[], prefix?: number[]): string { + return bsv.Utils.toBase58Check(bin, prefix); + }, + fromBase58Check: function (str: string, enc?: "hex", prefixLength?: number): { prefix: string | number[]; data: string | number[]; } { + return bsv.Utils.fromBase58Check(str, enc, prefixLength); + }, + getPreimage: function (tx: Transaction, subscript: LockingScript, inputAmount: number, inputIndex?: number, sighashType?: number): number[] { + + const txTarget = tx[TARGET] as bsv.Transaction; + + const subscriptTarget = subscript[TARGET] as bsv.LockingScript; + + const input = txTarget.inputs[inputIndex] + const otherInputs = txTarget.inputs.filter((_, index) => index !== inputIndex) + + const sourceTXID = input.sourceTXID || input.sourceTransaction?.id('hex') as string; + if (!sourceTXID) { + // Question: Should the library support use-cases where the source transaction is not provided? This is to say, is it ever acceptable for someone to sign an input spending some output from a transaction they have not provided? Some elements (such as the satoshi value and output script) are always required. A merkle proof is also always required, and verifying it (while also verifying that the claimed output is contained within the claimed transaction) is also always required. This seems to require the entire input transaction. + throw new Error( + 'The source transaction is needed for transaction signing.' + ) + } + + const preimage = bsv.TransactionSignature.format({ + sourceTXID: sourceTXID, + sourceOutputIndex: input.sourceOutputIndex, + sourceSatoshis: inputAmount, + transactionVersion: txTarget.version, + otherInputs, + inputIndex, + outputs: txTarget.outputs, + inputSequence: input.sequence, + subscript: subscriptTarget, + lockTime: tx.lockTime, + scope: sighashType + }) + + return preimage; + }, + num2bin: function (n: bigint, dataLen?: number): number[] { + const num = new bsv.BigNumber(n.toString()); + + if (typeof dataLen === 'undefined') { + return num.toSm('little'); + } + + const arr = num.toSm('little'); + + if (arr.length > dataLen) { + throw new Error(`${n} cannot fit in ${dataLen} byte[s]`); + } + + if (arr.length === dataLen) { + return arr; + } + + const paddingLen = dataLen - arr.length; + + let m = arr[arr.length - 1]; + + const rest = arr.slice(0, arr.length - 1); + if (num.isNeg()) { + // reset sign bit + m &= 0x7F; + } + + const padding = Array(paddingLen).fill(0); + if (num.isNeg()) { + padding[arr.length - 1] = 0x80 + } + return rest.concat([m]).concat(padding); + }, + bin2num: function (bin: number[]): bigint { + const bn = bsv.BigNumber.fromSm(bin, 'little'); + return BigInt(bn.toString()); + } + }; + PublicKey = { + fromPrivateKey: function (key: PrivateKey): PublicKey { + const k = Object.getPrototypeOf(key) as bsv.PrivateKey; + return createPublicKeyProxy(bsv.PublicKey.fromPrivateKey(k)) + }, + fromString: function (str: string): PublicKey { + return createPublicKeyProxy(bsv.PublicKey.fromString(str)) + }, + from: function (x: bigint | number | number[] | string | null, + y?: bigint | number | number[] | string | null): PublicKey { + + if (typeof x === 'bigint' && typeof y === 'bigint') { + return createPublicKeyProxy(new bsv.PublicKey(new bsv.BigNumber(x.toString()), new bsv.BigNumber(y.toString()))) + } + + if (typeof x === 'bigint' && typeof y !== 'bigint') { + return createPublicKeyProxy(new bsv.PublicKey(new bsv.BigNumber(x.toString()), y)) + } else if (typeof x !== 'bigint' && typeof y === 'bigint') { + return createPublicKeyProxy(new bsv.PublicKey(x, new bsv.BigNumber(y.toString()))) + } + + if (typeof x !== 'bigint' && typeof y !== 'bigint') { + return createPublicKeyProxy(new bsv.PublicKey(x, y)) + } + + }, + }; + PrivateKey = { + fromRandom: function (): PrivateKey { + return createPrivateKeyProxy(bsv.PrivateKey.fromRandom()) + }, + fromString: function (str: string, base: number | 'hex'): PrivateKey { + return createPrivateKeyProxy(bsv.PrivateKey.fromString(str, base)) + }, + fromWif: function (wif: string, prefixLength?: number): PrivateKey { + return createPrivateKeyProxy(bsv.PrivateKey.fromWif(wif, prefixLength)) + }, + + from: function (number: bigint | number | string | number[], + base?: number | 'be' | 'le' | 'hex', + endian?: 'be' | 'le', + modN?: 'apply' | 'nocheck' | 'error'): PrivateKey { + + if (typeof number === 'bigint') { + return createPrivateKeyProxy(new bsv.PrivateKey(new bsv.BigNumber(number.toString()), base, endian, modN)) + } + return createPrivateKeyProxy(new bsv.PrivateKey(number, base, endian, modN)) + }, + }; + + Transaction = { + fromHex: function (hex: string): Transaction { + return createTransactionProxy(bsv.Transaction.fromHex(hex)); + }, + fromBinary: function (bin: number[]): Transaction { + return createTransactionProxy(bsv.Transaction.fromBinary(bin)); + }, + from: function (version?: number, + inputs?: TransactionInput[], + outputs?: TransactionOutput[], + lockTime?: number, + metadata?: Record): Transaction { + + return createTransactionProxy(new bsv.Transaction(version, + inputs as unknown as (bsv.TransactionInput[]), + outputs as unknown as (bsv.TransactionOutput[]), + lockTime, metadata)); + } + } + + UnlockingScript = { + fromHex: function (hex: string): UnlockingScript { + return createScriptProxy(bsv.UnlockingScript.fromHex(hex)); + }, + fromASM: function (asm: string): UnlockingScript { + return createScriptProxy(bsv.UnlockingScript.fromASM(asm)); + }, + fromBinary: function (bin: number[]): UnlockingScript { + return createScriptProxy(bsv.UnlockingScript.fromBinary(bin)); + }, + from: function (chunks: ScriptChunk[] = []): UnlockingScript { + return createScriptProxy(new bsv.UnlockingScript(chunks)); + } + } + + LockingScript = { + fromHex: function (hex: string): LockingScript { + return createScriptProxy(bsv.LockingScript.fromHex(hex)); + }, + fromASM: function (asm: string): LockingScript { + return createScriptProxy(bsv.LockingScript.fromASM(asm)); + }, + fromBinary: function (bin: number[]): LockingScript { + return createScriptProxy(bsv.LockingScript.fromBinary(bin)); + }, + from: function (chunks: ScriptChunk[] = []): LockingScript { + return createScriptProxy(new bsv.LockingScript(chunks)); + } + } + + OP = OP + +} \ No newline at end of file diff --git a/src/chain/bsv/index.ts b/src/chain/bsv/index.ts new file mode 100644 index 00000000..710b31f9 --- /dev/null +++ b/src/chain/bsv/index.ts @@ -0,0 +1 @@ +export * from './factory' \ No newline at end of file diff --git a/src/chain/bsv/primitives/PrivateKey.ts b/src/chain/bsv/primitives/PrivateKey.ts new file mode 100644 index 00000000..7c5d827f --- /dev/null +++ b/src/chain/bsv/primitives/PrivateKey.ts @@ -0,0 +1,26 @@ + +import * as bsv from "@bsv/sdk"; +import { PrivateKey } from "../../base/primitives/PrivateKey"; +import { TARGET } from "../target"; + + +export default function createPrivateKeyProxy(key: bsv.PrivateKey): PrivateKey { + const handler = { + + get: function (target, prop) { + + if (prop === TARGET) { + return target + } + + if (prop === 'sign') { + return () => { + + } + } + return Reflect.get(target, prop); + } + }; + + return new Proxy(key, handler) as PrivateKey; +} \ No newline at end of file diff --git a/src/chain/bsv/primitives/PublicKey.ts b/src/chain/bsv/primitives/PublicKey.ts new file mode 100644 index 00000000..274c2183 --- /dev/null +++ b/src/chain/bsv/primitives/PublicKey.ts @@ -0,0 +1,19 @@ + +import * as bsv from "@bsv/sdk"; +import { PublicKey } from "../../base/primitives/PublicKey"; +import { TARGET } from "../target"; + + +export default function createPublicKeyProxy(key: bsv.PublicKey): PublicKey { + return new Proxy(key, { + + get: function (target, prop) { + + if (prop === TARGET) { + return target + } + + return Reflect.get(target, prop); + } + }) as unknown as PublicKey; +} \ No newline at end of file diff --git a/src/chain/bsv/primitives/Reader.ts b/src/chain/bsv/primitives/Reader.ts new file mode 100644 index 00000000..5279b403 --- /dev/null +++ b/src/chain/bsv/primitives/Reader.ts @@ -0,0 +1,17 @@ + +import * as bsv from "@bsv/sdk"; +import { Reader } from "../../base/primitives/Reader"; +import { TARGET } from "../target"; + + +export default function createReaderProxy(reader: bsv.Utils.Reader): Reader { + return new Proxy(reader, { + + get: function (target, prop) { + if (prop === TARGET) { + return target + } + return Reflect.get(target, prop); + } + }) as unknown as Reader; +} \ No newline at end of file diff --git a/src/chain/bsv/primitives/Writer.ts b/src/chain/bsv/primitives/Writer.ts new file mode 100644 index 00000000..1e5b872b --- /dev/null +++ b/src/chain/bsv/primitives/Writer.ts @@ -0,0 +1,22 @@ + +import * as bsv from "@bsv/sdk"; +import { Writer } from "../../base/primitives/Writer"; +import { TARGET } from "../target"; + + +export default function createWriterProxy(writer: bsv.Utils.Writer): Writer { + + + + return new Proxy(writer, { + + get: function (target, prop) { + + if (prop === TARGET) { + return target + } + + return Reflect.get(target, prop); + } + }) as unknown as Writer; +} \ No newline at end of file diff --git a/src/chain/bsv/script/script.ts b/src/chain/bsv/script/script.ts new file mode 100644 index 00000000..f19d9271 --- /dev/null +++ b/src/chain/bsv/script/script.ts @@ -0,0 +1,26 @@ + +import { Script as BSVScript, BigNumber, } from "@bsv/sdk"; +import { Script } from "../../base/script/Script"; +import { TARGET } from "../target"; + + +export default function createScriptProxy(script: BSVScript): Script { + + return new Proxy(script, { + // target represents the Person while prop represents + // proxy property. + get: function (target, prop) { + if (prop === TARGET) { + return target + } + if (prop === 'writeBn') { + return function (bn: bigint) { + return Reflect.apply(target[prop], this, [new BigNumber(bn.toString())]); + }; + } + + return Reflect.get(target, prop); + } + }) as unknown as Script; + +} \ No newline at end of file diff --git a/src/chain/bsv/target.ts b/src/chain/bsv/target.ts new file mode 100644 index 00000000..0eb3850d --- /dev/null +++ b/src/chain/bsv/target.ts @@ -0,0 +1 @@ +export const TARGET = Symbol('proxy_target_identity'); \ No newline at end of file diff --git a/src/chain/bsv/transaction/transaction.ts b/src/chain/bsv/transaction/transaction.ts new file mode 100644 index 00000000..d5140c01 --- /dev/null +++ b/src/chain/bsv/transaction/transaction.ts @@ -0,0 +1,20 @@ + +import { Transaction as BSVTransaction } from "@bsv/sdk"; +import { Transaction } from "../../base/transaction/Transaction"; +import { TARGET } from "../target"; + + +export default function createTransactionProxy(tx: BSVTransaction): Transaction { + + return new Proxy(tx, { + // target represents the Person while prop represents + // proxy property. + get: function (target, prop) { + if (prop === TARGET) { + return target + } + return Reflect.get(target, prop); + } + }) as unknown as Transaction; + +} \ No newline at end of file diff --git a/src/chain/chain.ts b/src/chain/chain.ts new file mode 100644 index 00000000..b9941d4b --- /dev/null +++ b/src/chain/chain.ts @@ -0,0 +1,44 @@ +import { Factory } from "./base/factory"; +import { BSVFactory } from "./bsv/factory"; + + + +export class Chain { + + static readonly BSV = 0; + static readonly MVC = 1; + + static readonly BTC = 1; + + private static instance: Factory; + + private constructor() { } + + static initFactory(C: Chain = Chain.BSV) { + + if (!Chain.instance) { + + const bsvFactory = new BSVFactory(); + + switch (C) { + case Chain.BSV: + Chain.instance = bsvFactory; + break; + default: + Chain.instance = bsvFactory; + + } + } else { + throw new Error(`already init`); + } + } + + static getFactory(): Factory { + if (!Chain.instance) { + Chain.initFactory(); + } + return Chain.instance; + } +} + + diff --git a/src/chain/index.ts b/src/chain/index.ts new file mode 100644 index 00000000..133c7732 --- /dev/null +++ b/src/chain/index.ts @@ -0,0 +1,2 @@ +export { Chain } from './chain' +export * from './base/index' \ No newline at end of file diff --git a/src/contract.ts b/src/contract.ts index 8088a5dd..962b8125 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -2,12 +2,12 @@ import { basename, dirname } from 'path'; import { ABIEntityType, Argument, LibraryEntity, ParamEntity, parseGenericType } from '.'; import { ContractEntity, getFullFilePath, loadSourceMapfromArtifact, OpCode, StaticEntity } from './compilerWrapper'; import { - ABICoder, ABIEntity, addScript, AliasEntity, Arguments, buildContractCode, checkNOPScript, CompileResult, findSrcInfoV1, findSrcInfoV2, FunctionCall, hash160, isArrayType, JSONParserSync, path2uri, resolveType, sha256, StructEntity, subscript, TypeResolver, uri2path + ABICoder, ABIEntity, addScript, AliasEntity, Arguments, buildContractCode, checkNOPScript, CompileResult, findSrcInfoV1, findSrcInfoV2, FunctionCall, hash160, isArrayType, JSONParserSync, path2uri, resolveType, sha256, StructEntity, subscript, TypeResolver, unpack, uri2path } from './internal'; import { Bytes, Int, isScryptType, SupportedParamType, SymbolType, TypeInfo } from './scryptTypes'; import Stateful from './stateful'; import { arrayTypeAndSize, checkSupportedParamType, flatternArg, hasGeneric, subArrayType } from './typeCheck'; -import { BigNumber, OP, Script, Transaction, UnlockingScript, Utils, Spend } from '@bsv/sdk'; +import { LockingScript, BigNumber, OP, Script, Transaction, UnlockingScript, Chain } from './chain'; /** * TxContext provides some context information of the current transaction, @@ -118,7 +118,7 @@ export class AbstractContract { nopScript: NOPScript | null; - get lockingScript(): Script { + get lockingScript(): LockingScript { if (this.hasInlineASMVars && this.hexTemplateInlineASM.size === 0) { throw new Error('Values for inline ASM variables have not yet been set! Cannot get locking script.'); @@ -132,9 +132,9 @@ export class AbstractContract { return addScript(this.codePart, this.dataPart); } - private _wrapNOPScript(lockingScript: Script) { + private _wrapNOPScript(lockingScript: LockingScript) { if (this.nopScript) { - const clone = Script.fromBinary(this.nopScript.toBinary()) + const clone = Chain.getFactory().LockingScript.fromBinary(this.nopScript.toBinary()) return addScript(clone, lockingScript); } @@ -192,7 +192,7 @@ export class AbstractContract { if (asmVarValues) { for (const key in asmVarValues) { const val = asmVarValues[key]; - this.hexTemplateInlineASM.set(`<${key.startsWith('$') ? key.substring(1) : key}>`, Script.fromASM(val).toHex()); + this.hexTemplateInlineASM.set(`<${key.startsWith('$') ? key.substring(1) : key}>`, Chain.getFactory().LockingScript.fromASM(val).toHex()); } } @@ -212,7 +212,7 @@ export class AbstractContract { for (const entry of this.hexTemplateInlineASM.entries()) { const name = entry[0].replace('<', '').replace('>', ''); const value = entry[1]; - result[name] = Script.fromHex(value).toASM(); + result[name] = Chain.getFactory().LockingScript.fromHex(value).toASM(); } return result; @@ -263,7 +263,7 @@ export class AbstractContract { } }); - return addScript(this.codePart, Script.fromHex(Stateful.buildState(newState, false, this.resolver))); + return addScript(this.codePart, Chain.getFactory().LockingScript.fromHex(Stateful.buildState(newState, false, this.resolver))); } run_verify(unlockingScript: UnlockingScript): VerifyResult { @@ -271,7 +271,7 @@ export class AbstractContract { let tx: Transaction; if (this._txContext && this._txContext.tx) { - tx = typeof this._txContext.tx === 'string' ? Transaction.fromHex(this._txContext.tx) : this._txContext.tx; + tx = typeof this._txContext.tx === 'string' ? Chain.getFactory().Transaction.fromHex(this._txContext.tx) : this._txContext.tx; } else { const sourceTx = new Transaction(1, [], [{ lockingScript: this.lockingScript, @@ -475,11 +475,11 @@ export class AbstractContract { if (AbstractContract.isStateful(this)) { const state = Stateful.buildState(this.statePropsArgs, this.isGenesis, this.resolver); - return Script.fromHex(state); + return Chain.getFactory().LockingScript.fromHex(state); } if (this._dataPartInHex) { - return Script.fromHex(this._dataPartInHex); + return Chain.getFactory().LockingScript.fromHex(this._dataPartInHex); } } @@ -510,7 +510,7 @@ export class AbstractContract { throw new Error('should not use `setDataPartInASM` for a stateful contract, using `setDataPartInHex`'); } const dataPartInASM = asm.trim(); - this.setDataPartInHex(Script.fromASM(dataPartInASM).toHex()); + this.setDataPartInHex(Chain.getFactory().LockingScript.fromASM(dataPartInASM).toHex()); } /** @@ -527,7 +527,7 @@ export class AbstractContract { } prependNOPScript(nopScript: NOPScript | null): void { - if (nopScript instanceof Script) { + if (nopScript) { checkNOPScript(nopScript); } @@ -541,8 +541,8 @@ export class AbstractContract { get codePart(): Script { const contractScript = this.scriptedConstructor.toScript(); // note: do not trim the trailing space - const clone = Script.fromBinary(contractScript.toBinary()) - return addScript(this._wrapNOPScript(clone), Script.fromHex('6a')); + const clone = Chain.getFactory().LockingScript.fromBinary(contractScript.toBinary()) + return addScript(this._wrapNOPScript(clone), Chain.getFactory().LockingScript.fromHex('6a')); } get codeHash(): string { @@ -737,7 +737,7 @@ export class AbstractContract { static fromASM(asm: string): AbstractContract { - return this.fromHex(Script.fromASM(asm).toHex()); + return this.fromHex(Chain.getFactory().LockingScript.fromASM(asm).toHex()); } static fromHex(hex: string): AbstractContract { @@ -751,7 +751,7 @@ export class AbstractContract { static fromTransaction(hex: string, outputIndex = 0): AbstractContract { - const tx = Transaction.fromHex(hex); + const tx = Chain.getFactory().Transaction.fromHex(hex); return this.fromHex(tx.outputs[outputIndex].lockingScript.toHex()); } @@ -796,8 +796,7 @@ export class AbstractContract { // sort the map by the result of flattenSha256 of the key private static sortmap(map: Map, keyType: string): Map { return new Map([...map.entries()].sort((a, b) => { - return BigNumber.fromSm(Utils.toArray(this.flattenSha256(a[0], keyType)), 'little') - .cmp(BigNumber.fromSm(Utils.toArray(this.flattenSha256(b[0], keyType)), 'little')); + return unpack(this.flattenSha256(a[0], keyType)) - unpack(this.flattenSha256(a[0], keyType)); })); } diff --git a/src/utils.ts b/src/utils.ts index a4da51c0..f4927ce3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,9 +6,11 @@ import { decode } from '@jridgewell/sourcemap-codec'; import { fileURLToPath, pathToFileURL } from 'url'; import { ABIEntity, LibraryEntity } from '.'; import { compileAsync, OpCode } from './compilerWrapper'; -import { AbstractContract, compile, CompileResult, findCompiler, getValidatedHexString, ScryptType, StructEntity, SupportedParamType, } from './internal'; +import { AbstractContract, compile, CompileResult, findCompiler, getValidatedHexString, hash256, ScryptType, StructEntity, SupportedParamType, } from './internal'; import { arrayTypeAndSizeStr, isGenericType, parseGenericType } from './typeCheck'; -import { PrivateKey, Transaction, BigNumber, LockingScript, TransactionSignature, Hash, UnlockingScript, Script, Utils } from '@bsv/sdk' + +import { UnlockingScript, PrivateKey, LockingScript, Chain, Transaction, BigNumber, Script } from './chain' + /** * decimal or hex int to little-endian signed magnitude @@ -90,6 +92,11 @@ export function toHex(x: { toString(format: 'hex'): string }): string { return x.toString('hex'); } +export function toArray(x: string): number[] { + return Chain.getFactory().Utils.toArray(x); +} + + export function utf82Hex(val: string): string { const encoder = new TextEncoder(); const uint8array = encoder.encode(val); @@ -166,39 +173,14 @@ export function signTx(tx: Transaction, privateKey: PrivateKey, subscript: Locki ) const sigForScript = sig.toChecksigFormat() - return Utils.toHex(sigForScript); + return toHex(sigForScript); } -export function getPreimage(tx: Transaction, subscript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number): string { - - const input = tx.inputs[inputIndex] - const otherInputs = tx.inputs.filter((_, index) => index !== inputIndex) - - const sourceTXID = input.sourceTXID || input.sourceTransaction?.id('hex') as string; - if (!sourceTXID) { - // Question: Should the library support use-cases where the source transaction is not provided? This is to say, is it ever acceptable for someone to sign an input spending some output from a transaction they have not provided? Some elements (such as the satoshi value and output script) are always required. A merkle proof is also always required, and verifying it (while also verifying that the claimed output is contained within the claimed transaction) is also always required. This seems to require the entire input transaction. - throw new Error( - 'The source transaction is needed for transaction signing.' - ) - } - - const preimage = TransactionSignature.format({ - sourceTXID: sourceTXID, - sourceOutputIndex: input.sourceOutputIndex, - sourceSatoshis: inputAmount, - transactionVersion: tx.version, - otherInputs, - inputIndex, - outputs: tx.outputs, - inputSequence: input.sequence, - subscript: subscript, - lockTime: tx.lockTime, - scope: sighashType - }) +export function getPreimage(tx: Transaction, subscript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number = 65): string { - return Utils.toHex(preimage); + return toHex(Chain.getFactory().Utils.getPreimage(tx, subscript, inputAmount, inputIndex, sighashType)); } const MSB_THRESHOLD = 0x7e; @@ -214,7 +196,7 @@ export function getLowSPreimage(tx: Transaction, lockingScript: LockingScript, i for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) { const preimage = getPreimage(tx, lockingScript, inputAmount, inputIndex, sighashType); - const sighash = Hash.hash256(preimage); + const sighash = hash256(preimage); const msb = sighash[0] if (msb < MSB_THRESHOLD && hashIsPositiveNumber(sighash)) { return preimage; @@ -469,7 +451,7 @@ export function buildContractCode(hexTemplateArgs: Map, hexTempl lsHex = lsHex.replace(new RegExp(`${escapeRegExp(name)}`, 'g'), value); } - return LockingScript.fromHex(lsHex); + return Chain.getFactory().LockingScript.fromHex(lsHex); } @@ -494,7 +476,7 @@ export function parseAbiFromUnlockingScript(contract: AbstractContract, hex: str return pubFunAbis[0]; } - const script = UnlockingScript.fromHex(hex); + const script = Chain.getFactory().UnlockingScript.fromHex(hex); const usASM = script.toASM() as string; diff --git a/test/abi.test.ts b/test/abi.test.ts index eff87979..7155f120 100644 --- a/test/abi.test.ts +++ b/test/abi.test.ts @@ -2,18 +2,19 @@ import { assert, expect } from 'chai'; import { newTx, loadArtifact } from './helper'; import { FunctionCall } from '../src/abi'; import { buildContractClass, VerifyResult } from '../src/contract'; -import { bsv, signTx, toHex } from '../src/utils'; +import { signTx, toHex } from '../src/utils'; import { PubKey, Sig, Ripemd160, Sha256 } from '../src/scryptTypes'; +import { PrivateKey, Script } from '@bsv/sdk'; -const privateKey = bsv.PrivateKey.fromRandom(bsv.Networks.testnet); -const publicKey = privateKey.publicKey; -const pubKeyHash = bsv.crypto.Hash.sha256ripemd160(publicKey.toBuffer()); +const privateKey = PrivateKey.fromRandom(); +const publicKey = privateKey.toPublicKey() +const pubKeyHash = publicKey.toHash('hex') as string const inputSatoshis = 100000; const tx = newTx(inputSatoshis); const jsonArtifact = loadArtifact('p2pkh.json'); const DemoP2PKH = buildContractClass(jsonArtifact); -const p2pkh = new DemoP2PKH(Ripemd160(toHex(pubKeyHash))); +const p2pkh = new DemoP2PKH(Ripemd160(pubKeyHash)); const personArtifact = loadArtifact('person.json'); const PersonContract = buildContractClass(personArtifact); @@ -41,6 +42,7 @@ describe('FunctionCall', () => { describe('when it is the contract constructor', () => { before(() => { + p2pkh.txContext = { inputSatoshis, tx, inputIndex: 0 } target = new FunctionCall('constructor', { contract: p2pkh, lockingScript: p2pkh.lockingScript, args: [{ name: 'pubKeyHash', @@ -73,7 +75,7 @@ describe('FunctionCall', () => { sig = Sig(signTx(tx, privateKey, p2pkh.lockingScript, inputSatoshis)); pubkey = PubKey(toHex(publicKey)); target = new FunctionCall('unlock', { - contract: p2pkh, unlockingScript: bsv.Script.fromASM([sig, pubkey].join(' ')), args: [{ + contract: p2pkh, unlockingScript: Script.fromASM([sig, pubkey].join(' ')), args: [{ name: 'sig', type: 'Sig', value: sig @@ -87,7 +89,7 @@ describe('FunctionCall', () => { describe('toHex() / toString()', () => { it('should return the unlocking script in hex', () => { - assert.equal(target.toHex(), bsv.Script.fromASM(target.toASM()).toHex()); + assert.equal(target.toHex(), Script.fromASM(target.toASM()).toHex()); }) }) @@ -114,7 +116,7 @@ describe('FunctionCall', () => { describe('verify()', () => { it('should return true if params are appropriate', () => { // has no txContext in binding contract - result = target.verify({ inputSatoshis, tx, inputIndex: 0 }); + result = target.verify(); assert.isTrue(result.success, result.error); // has txContext in binding contract @@ -125,23 +127,27 @@ describe('FunctionCall', () => { }) it('should fail if param `inputSatoshis` is incorrect', () => { - result = target.verify({ inputSatoshis: inputSatoshis + 1, tx, inputIndex: 0 }); + p2pkh.txContext = { inputSatoshis: inputSatoshis + 1, tx, inputIndex: 0 } + result = target.verify(); assert.isFalse(result.success, result.error); - result = target.verify({ inputSatoshis: inputSatoshis - 1, tx, inputIndex: 0 }); + p2pkh.txContext = { inputSatoshis: inputSatoshis - 1, tx, inputIndex: 0 } + result = target.verify(); assert.isFalse(result.success, result.error); }) it('should fail if param `txContext` is incorrect', () => { // missing txContext expect(() => { + p2pkh.txContext = undefined; target.verify() - }).to.throw('should provide txContext.tx when verify') + }).to.throw('should provide txContext when verify') // incorrect txContext.tx - tx.nLockTime = tx.nLockTime + 1; - result = target.verify({ inputSatoshis, tx, inputIndex: 0 }); + tx.lockTime = tx.lockTime + 1; + p2pkh.txContext = { inputSatoshis, tx, inputIndex: 0 } + result = target.verify(); assert.isFalse(result.success, result.error); - tx.nLockTime = tx.nLockTime - 1; //reset + tx.lockTime = tx.lockTime - 1; //reset }) }) }) @@ -615,8 +621,8 @@ describe('string as bigInt', () => { expect(funCallClone.methodName).to.equal('unlock'); - - result = funCallClone.verify({ inputSatoshis, tx, inputIndex: 0 }) + p2pkhClone.txContext = { inputSatoshis, tx, inputIndex: 0 }; + result = funCallClone.verify() expect(result.success).to.be.true; diff --git a/test/accumulatorMultiSig.test.ts b/test/accumulatorMultiSig.test.ts index 47330b73..0c54c424 100644 --- a/test/accumulatorMultiSig.test.ts +++ b/test/accumulatorMultiSig.test.ts @@ -1,28 +1,29 @@ import { expect } from 'chai'; -import { bsv, buildContractClass, PubKey, Ripemd160, Sig, signTx, toHex } from '../src'; +import { buildContractClass, PubKey, Ripemd160, Sig, signTx, toHex } from '../src'; import { loadArtifact, newTx } from './helper'; +import { PrivateKey, Transaction } from '@bsv/sdk'; const inputSatoshis = 10000; const inputIndex = 0; describe('Test SmartContract `AccumulatorMultiSig`', () => { - const privateKey1 = bsv.PrivateKey.fromRandom(bsv.Networks.testnet); - const publicKey1 = bsv.PublicKey.fromPrivateKey(privateKey1); - const publicKeyHash1 = bsv.crypto.Hash.sha256ripemd160(publicKey1.toBuffer()); + const privateKey1 = PrivateKey.fromRandom(); + const publicKey1 = privateKey1.toPublicKey(); + const publicKeyHash1 = publicKey1.toHash('hex'); - const privateKey2 = bsv.PrivateKey.fromRandom(bsv.Networks.testnet); - const publicKey2 = bsv.PublicKey.fromPrivateKey(privateKey2); - const publicKeyHash2 = bsv.crypto.Hash.sha256ripemd160(publicKey2.toBuffer()); + const privateKey2 = PrivateKey.fromRandom(); + const publicKey2 = privateKey2.toPublicKey() + const publicKeyHash2 = publicKey2.toHash('hex') - const privateKey3 = bsv.PrivateKey.fromRandom(bsv.Networks.testnet); - const publicKey3 = bsv.PublicKey.fromPrivateKey(privateKey3); - const publicKeyHash3 = bsv.crypto.Hash.sha256ripemd160(publicKey3.toBuffer()); + const privateKey3 = PrivateKey.fromRandom(); + const publicKey3 = privateKey3.toPublicKey() + const publicKeyHash3 = publicKey3.toHash('hex') - const privateKeyWrong = bsv.PrivateKey.fromRandom(bsv.Networks.testnet); - const publicKeyWrong = bsv.PublicKey.fromPrivateKey(privateKeyWrong); + const privateKeyWrong = PrivateKey.fromRandom(); + const publicKeyWrong = privateKeyWrong.toPublicKey() let accumulatorMultiSig, result @@ -45,60 +46,61 @@ describe('Test SmartContract `AccumulatorMultiSig`', () => { const sig3 = signTx(tx, privateKey3, accumulatorMultiSig.lockingScript, inputSatoshis); const context = { tx, inputIndex, inputSatoshis } + accumulatorMultiSig.txContext = context; let result = accumulatorMultiSig.main([PubKey(toHex(publicKey1)), PubKey(toHex(publicKey2)), PubKey(toHex(publicKey3))], - [Sig(sig1), Sig(sig2), Sig(sig3)], [true, true, true]).verify(context); + [Sig(sig1), Sig(sig2), Sig(sig3)], [true, true, true]).verify(); expect(result.success, result.error).to.eq(true); }) - it('should successfully with all two right.', () => { + // it('should successfully with all two right.', () => { - const callTx = new bsv.Transaction().addDummyInput(accumulatorMultiSig.lockingScript, inputSatoshis) - .dummyChange() - .setInputScript({ - inputIndex, - privateKey: [privateKey1, privateKey2, privateKey3] - }, (tx: bsv.Transaction) => { - const sigs = tx.getSignature(inputIndex); - return accumulatorMultiSig.main([PubKey(toHex(publicKey1)), PubKey(toHex(publicKey2)), PubKey(toHex(publicKey3))], - [Sig(sigs[0]), Sig(sigs[1]), Sig(sigs[2])], [true, true, true]).toScript(); - }) + // const callTx = new Transaction().addInput(accumulatorMultiSig.lockingScript, inputSatoshis) + // .dummyChange() + // .setInputScript({ + // inputIndex, + // privateKey: [privateKey1, privateKey2, privateKey3] + // }, (tx: bsv.Transaction) => { + // const sigs = tx.getSignature(inputIndex); + // return accumulatorMultiSig.main([PubKey(toHex(publicKey1)), PubKey(toHex(publicKey2)), PubKey(toHex(publicKey3))], + // [Sig(sigs[0]), Sig(sigs[1]), Sig(sigs[2])], [true, true, true]).toScript(); + // }) - .seal() + // .seal() - // verify all tx inputs - expect(callTx.verify()).to.be.true + // // verify all tx inputs + // expect(callTx.verify()).to.be.true - // just verify the contract inputs - expect(callTx.verifyInputScript(0).success).to.true + // // just verify the contract inputs + // expect(callTx.verifyInputScript(0).success).to.true - }) + // }) - it('should fail with only one right.', () => { + // it('should fail with only one right.', () => { - const callTx = new bsv.Transaction() - .addDummyInput(accumulatorMultiSig.lockingScript, inputSatoshis) - .dummyChange() - .setInputScript({ - inputIndex, - privateKey: [privateKey1, privateKeyWrong, privateKeyWrong] - }, (tx: bsv.Transaction) => { - const sigs = tx.getSignature(inputIndex); - return accumulatorMultiSig.main([PubKey(toHex(publicKey1)), PubKey(toHex(publicKey2)), PubKey(toHex(publicKey3))], - [Sig(sigs[0]), Sig(sigs[1]), Sig(sigs[2])], [true, false, false]).toScript(); - }) - .seal() + // const callTx = new Transaction() + // .addDummyInput(accumulatorMultiSig.lockingScript, inputSatoshis) + // .dummyChange() + // .setInputScript({ + // inputIndex, + // privateKey: [privateKey1, privateKeyWrong, privateKeyWrong] + // }, (tx: bsv.Transaction) => { + // const sigs = tx.getSignature(inputIndex); + // return accumulatorMultiSig.main([PubKey(toHex(publicKey1)), PubKey(toHex(publicKey2)), PubKey(toHex(publicKey3))], + // [Sig(sigs[0]), Sig(sigs[1]), Sig(sigs[2])], [true, false, false]).toScript(); + // }) + // .seal() - // verify all tx inputs - expect(callTx.verify()).to.be.eq('transaction input 0 VerifyError: SCRIPT_ERR_EVAL_FALSE_IN_STACK') + // // verify all tx inputs + // expect(callTx.verify()).to.be.eq('transaction input 0 VerifyError: SCRIPT_ERR_EVAL_FALSE_IN_STACK') - // just verify the contract inputs - expect(callTx.verifyInputScript(0).success).to.false + // // just verify the contract inputs + // expect(callTx.verifyInputScript(0).success).to.false - }) + // }) }) \ No newline at end of file diff --git a/test/counter.test.ts b/test/counter.test.ts index 4ba3fb28..f8bd5aed 100644 --- a/test/counter.test.ts +++ b/test/counter.test.ts @@ -1,12 +1,13 @@ import { expect } from 'chai' -import { loadArtifact } from './helper' +import { loadArtifact, newTx } from './helper' import { buildContractClass } from '../src/contract' -import { bsv, num2bin, SigHashPreimage } from '../src' +import { getPreimage, num2bin, SigHashPreimage } from '../src' +import { LockingScript } from '@bsv/sdk' describe('test.Counter', () => { - it('should unlock success', () => { + it('should unlock success', async () => { const Counter = buildContractClass(loadArtifact('counter.json')) let counter = new Counter() @@ -14,7 +15,27 @@ describe('test.Counter', () => { counter.setDataPartInASM('00') - let callTx = new bsv.Transaction() + const tx = newTx(1000, counter.lockingScript); + + const newLockingScript = LockingScript.fromASM([counter.codePart.toASM(), num2bin(1n, 1)].join(' ')) + tx.addOutput({ + lockingScript: newLockingScript, + change: true + }) + + + const preimage = getPreimage(tx, counter.lockingScript, 1000, 0, 65) + + tx.inputs[0].unlockingScript = counter.increment(SigHashPreimage(preimage), BigInt(1)).toScript(); + + counter.txContext = { tx, inputIndex: 0, inputSatoshis: 1000 } + + const result = counter.increment(SigHashPreimage(preimage), BigInt(1)).verify(); + + + console.log('result', result) + + let callTx = new Transaction() .addDummyInput(counter.lockingScript, 1000) .setOutput(0, (tx) => { const newLockingScript = [counter.codePart.toASM(), num2bin(1n, 1)].join(' ') @@ -31,84 +52,84 @@ describe('test.Counter', () => { // verify all tx inputs expect(callTx.verify()).to.be.true - // just verify the contract inputs - expect(callTx.verifyInputScript(0).success).to.true + // // just verify the contract inputs + // expect(callTx.verifyInputScript(0).success).to.true - let callTx1 = new bsv.Transaction() - .addInputFromPrevTx(callTx) - .setOutput(0, (tx) => { - const newLockingScript = [counter.codePart.toASM(), num2bin(2n, 1)].join(' ') - const newAmount = tx.inputAmount - tx.getEstimateFee(); - return new bsv.Transaction.Output({ - script: bsv.Script.fromASM(newLockingScript), - satoshis: newAmount - }) - }) - .setInputScript(0, (tx) => { - return counter.increment(SigHashPreimage(tx.getPreimage(0)), BigInt(tx.getOutputAmount(0))).toScript(); - }) - .seal(); - // verify all tx inputs - expect(callTx1.verify()).to.be.true + // let callTx1 = new bsv.Transaction() + // .addInputFromPrevTx(callTx) + // .setOutput(0, (tx) => { + // const newLockingScript = [counter.codePart.toASM(), num2bin(2n, 1)].join(' ') + // const newAmount = tx.inputAmount - tx.getEstimateFee(); + // return new bsv.Transaction.Output({ + // script: bsv.Script.fromASM(newLockingScript), + // satoshis: newAmount + // }) + // }) + // .setInputScript(0, (tx) => { + // return counter.increment(SigHashPreimage(tx.getPreimage(0)), BigInt(tx.getOutputAmount(0))).toScript(); + // }) + // .seal(); + // // verify all tx inputs + // expect(callTx1.verify()).to.be.true - // just verify the contract inputs - expect(callTx1.verifyInputScript(0).success).to.true + // // just verify the contract inputs + // expect(callTx1.verifyInputScript(0).success).to.true }) - it('should unlock failed', () => { + // it('should unlock failed', () => { - const Counter = buildContractClass(loadArtifact('counter.json')) - let counter = new Counter() + // const Counter = buildContractClass(loadArtifact('counter.json')) + // let counter = new Counter() - counter.setDataPartInASM('00') + // counter.setDataPartInASM('00') - let callTx = new bsv.Transaction() - .addDummyInput(counter.lockingScript, 1000) - .setOutput(0, (tx) => { - const newLockingScript = [counter.codePart.toASM(), num2bin(1n, 1)].join(' ') - const newAmount = tx.inputAmount - tx.getEstimateFee(); - return new bsv.Transaction.Output({ - script: bsv.Script.fromASM(newLockingScript), - satoshis: newAmount - }) - }) - .setInputScript(0, (tx) => { - return counter.increment(SigHashPreimage(tx.getPreimage(0)), 1n).toScript(); - }) - .seal(); + // let callTx = new bsv.Transaction() + // .addDummyInput(counter.lockingScript, 1000) + // .setOutput(0, (tx) => { + // const newLockingScript = [counter.codePart.toASM(), num2bin(1n, 1)].join(' ') + // const newAmount = tx.inputAmount - tx.getEstimateFee(); + // return new bsv.Transaction.Output({ + // script: bsv.Script.fromASM(newLockingScript), + // satoshis: newAmount + // }) + // }) + // .setInputScript(0, (tx) => { + // return counter.increment(SigHashPreimage(tx.getPreimage(0)), 1n).toScript(); + // }) + // .seal(); - // verify all tx inputs - expect(callTx.verify()).to.be.eq('transaction input 0 VerifyError: SCRIPT_ERR_EVAL_FALSE_IN_STACK') - - // just verify the contract inputs - const result = callTx.verifyInputScript(0) - expect(result).to.deep.eq({ - success: false, - error: "SCRIPT_ERR_EVAL_FALSE_IN_STACK", - failedAt: { - fExec: true, - opcode: 106, - pc: 1011 - } - }) + // // verify all tx inputs + // expect(callTx.verify()).to.be.eq('transaction input 0 VerifyError: SCRIPT_ERR_EVAL_FALSE_IN_STACK') - const launchConfigUri = counter.genLaunchConfig({ - tx: callTx, - inputIndex: 0, - inputSatoshis: 1000 - }); + // // just verify the contract inputs + // const result = callTx.verifyInputScript(0) + // expect(result).to.deep.eq({ + // success: false, + // error: "SCRIPT_ERR_EVAL_FALSE_IN_STACK", + // failedAt: { + // fExec: true, + // opcode: 106, + // pc: 1011 + // } + // }) - expect(launchConfigUri).to.includes("Launch Debugger") + // const launchConfigUri = counter.genLaunchConfig({ + // tx: callTx, + // inputIndex: 0, + // inputSatoshis: 1000 + // }); - expect(counter.fmtError(result)).to.includes("counter.scrypt#20") - expect(counter.fmtError(result)).to.includes("fails at OP_RETURN") + // expect(launchConfigUri).to.includes("Launch Debugger") + // expect(counter.fmtError(result)).to.includes("counter.scrypt#20") + // expect(counter.fmtError(result)).to.includes("fails at OP_RETURN") - }) + + // }) }) diff --git a/test/helper.ts b/test/helper.ts index 69f6e9b2..e2be9ed0 100644 --- a/test/helper.ts +++ b/test/helper.ts @@ -1,7 +1,7 @@ import { join } from 'path'; import { readFileSync, existsSync } from 'fs'; -import { bsv } from '../src/utils'; import { Artifact } from '../src/contract'; +import { LockingScript, Transaction, UnlockingScript } from '@bsv/sdk'; export function loadArtifact(fileName: string): Artifact { return JSON.parse(readFileSync(join(__dirname, "../out/", fileName)).toString()); } @@ -18,14 +18,20 @@ export function getInvalidContractFilePath(fileName: string): string { return join(__dirname, 'fixture', 'invalid', fileName); } -export function newTx(inputSatoshis: number) { - const utxo = { - txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', - outputIndex: 0, - script: '', // placeholder +export function newTx(inputSatoshis: number = 100000, lockingScript: LockingScript = new LockingScript()) { + + const sourceTx = new Transaction(1, [], [{ + lockingScript: lockingScript, satoshis: inputSatoshis - }; - return new bsv.Transaction().from(utxo); + }], 0) + + const spendTx = new Transaction(1, [{ + sourceTransaction: sourceTx, + sourceOutputIndex: 0, + sequence: 0xffffffff, + }], [], 0) + + return spendTx; } export function excludeMembers(o: any, members: string[]) { diff --git a/test/tx.test.ts b/test/tx.test.ts new file mode 100644 index 00000000..45b6960b --- /dev/null +++ b/test/tx.test.ts @@ -0,0 +1,26 @@ +import { OP } from "@bsv/sdk"; +import { Chain } from "../src/chain/chain"; + + +const factory = Chain.getFactory(Chain.BSV); + +const s = factory.LockingScript.fromHex('76a914212771cc264264057238cc3b98a03ddd9aa3a31c88ac'); + +s.writeNumber(3).writeBn(BigInt(30)); +s.writeOpCode(OP.OP_2SWAP); + +const ss = factory.UnlockingScript.fromHex('020111'); + +s.writeScript(ss); + +console.log(s, s.toASM()) + +const tx = factory.Transaction.fromHex('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1a034fcd0c2f7461616c2e636f6d2ffd54e6dd4fe37955f2600000ffffffff0161cf4025000000001976a914522cf9e7626d9bd8729e5a1398ece40dad1b6a2f88ac00000000'); + + +const tx1 = factory.Transaction.from(); + + +const key = factory.PrivateKey.fromRandom(); +// tx.addInput() +console.log(key.toAddress()) From 8e355cb1cb5a40dec27b517738c583ebc61a4d61 Mon Sep 17 00:00:00 2001 From: hh Date: Thu, 11 Apr 2024 11:03:22 +0800 Subject: [PATCH 3/3] update interface --- src/abi.ts | 8 +- src/builtins.ts | 6 +- src/chain/base/factory.ts | 39 +++++- src/chain/base/index.ts | 3 +- src/chain/base/primitives/PrivateKey.ts | 2 +- src/chain/base/primitives/PublicKey.ts | 2 - src/chain/base/primitives/Reader.ts | 8 +- src/chain/base/primitives/Signature.ts | 16 ++- src/chain/base/primitives/Writer.ts | 8 +- src/chain/base/script/Script.ts | 6 +- src/chain/base/script/Spend.ts | 7 + src/chain/base/transaction/Broadcaster.ts | 4 +- src/chain/base/transaction/FeeModel.ts | 2 +- src/chain/bsv/factory.ts | 142 ++++++++++----------- src/chain/bsv/primitives/PrivateKey.ts | 8 +- src/chain/bsv/primitives/PublicKey.ts | 5 +- src/chain/bsv/primitives/Reader.ts | 5 +- src/chain/bsv/primitives/Signature.ts | 19 +++ src/chain/bsv/primitives/Utils.ts | 149 ++++++++++++++++++++++ src/chain/bsv/primitives/Writer.ts | 7 +- src/chain/bsv/script/Spend.ts | 19 +++ src/chain/bsv/script/script.ts | 18 ++- src/chain/bsv/transaction/transaction.ts | 8 +- src/contract.ts | 44 +++++-- src/deserializer.ts | 14 +- src/index.ts | 2 +- src/serializer.ts | 24 +--- src/stateful.ts | 51 ++++---- src/utils.ts | 125 ++---------------- test/abi.test.ts | 8 +- test/helper.ts | 9 +- test/tx.test.ts | 2 +- test/utils.test.ts | 29 +---- 33 files changed, 454 insertions(+), 345 deletions(-) create mode 100644 src/chain/base/script/Spend.ts create mode 100644 src/chain/bsv/primitives/Signature.ts create mode 100644 src/chain/bsv/primitives/Utils.ts create mode 100644 src/chain/bsv/script/Spend.ts diff --git a/src/abi.ts b/src/abi.ts index 773039b6..c5fab2d2 100644 --- a/src/abi.ts +++ b/src/abi.ts @@ -7,7 +7,7 @@ import { SupportedParamType, TypeResolver, Int } from './scryptTypes'; import { toScriptHex } from './serializer'; import Stateful from './stateful'; import { flatternArg } from './typeCheck'; -import { asm2int, buildContractCode, int2Asm } from './utils'; +import { buildContractCode } from './utils'; import { Chain, UnlockingScript, LockingScript, Script, ScriptChunk } from './chain'; @@ -538,7 +538,7 @@ export class ABICoder { if (this.abi.length > 2 && entity.index !== undefined) { // selector when there are multiple public functions const pubFuncIndex = entity.index; - hex += `${Chain.getFactory().UnlockingScript.fromASM(int2Asm(pubFuncIndex.toString())).toHex()}`; + hex += `${Chain.getFactory().Utils.num2bin(BigInt(pubFuncIndex))}`; } return new FunctionCall(name, { contract, unlockingScript: Chain.getFactory().UnlockingScript.fromHex(hex), args: entity.params.map((param, index) => ({ @@ -588,9 +588,9 @@ export class ABICoder { const pubFuncIndexASM = usASM.slice(usASM.lastIndexOf(' ') + 1); - const pubFuncIndex = asm2int(pubFuncIndexASM); + const pubFuncIndex = Chain.getFactory().Utils.asm2num(pubFuncIndexASM); - entity = this.abi.find(entity => entity.index === pubFuncIndex); + entity = this.abi.find(entity => entity.index === Number(pubFuncIndex)); } if (!entity) { diff --git a/src/builtins.ts b/src/builtins.ts index 9780f551..323b503d 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -1,4 +1,4 @@ -import { Bytes, Int, Ripemd160 } from '.'; +import { Bytes, Int, Ripemd160, toHex } from '.'; import { Chain, LockingScript } from './chain'; /** @@ -34,10 +34,6 @@ export function bin2num(hex: string): bigint { return Chain.getFactory().Utils.bin2num(bin); } -export function toHex(msg: number[]): string { - return Chain.getFactory().Utils.toHex(msg); -} - export function and(a: Int, b: Int): Int { const size1 = pack(a).length / 2; const size2 = pack(b).length / 2; diff --git a/src/chain/base/factory.ts b/src/chain/base/factory.ts index eaa76e4f..7143a754 100644 --- a/src/chain/base/factory.ts +++ b/src/chain/base/factory.ts @@ -1,4 +1,4 @@ -import { ScriptChunk } from "./script/Script"; +import { Script, ScriptChunk } from "./script/Script"; import { UnlockingScript } from "./script/UnlockingScript"; import { LockingScript } from "./script/LockingScript"; @@ -10,6 +10,7 @@ import { TransactionOutput } from "./transaction/TransactionOutput"; import { Reader } from "./primitives/Reader"; import { Writer } from "./primitives/Writer"; import { IOP } from "./iop"; +import { Spend } from "./script/Spend"; interface ITransaction { @@ -30,6 +31,13 @@ interface IUnlockingScript { from: (chunks?: ScriptChunk[]) => UnlockingScript, } +interface IScript { + fromHex: (hex: string) => Script, + fromASM: (asm: string) => Script, + fromBinary: (bin: number[]) => Script, + from: (chunks?: ScriptChunk[]) => Script, +} + interface ILockingScript { fromHex: (hex: string) => LockingScript, fromASM: (asm: string) => LockingScript, @@ -90,6 +98,12 @@ interface IUtils { num2bin(n: bigint, dataLen?: number): number[]; bin2num(bin: number[]): bigint; + + num2asm(n: bigint): string; + + asm2num(asm: string): bigint; + + signTx(tx: Transaction, privateKey: PrivateKey, subscript: LockingScript, inputAmount: number, inputIndex?: number, sighashType?: number): string; } interface IReader { @@ -101,16 +115,37 @@ interface IWriter { } +interface ISpend { + from: (params: { + sourceTXID: string + sourceOutputIndex: number + sourceSatoshis: number + lockingScript: LockingScript + transactionVersion: number + otherInputs: TransactionInput[] + outputs: TransactionOutput[] + unlockingScript: UnlockingScript + inputSequence: number + inputIndex: number + lockTime: number + }) => Spend; +} + + + export interface Factory { Transaction: ITransaction, UnlockingScript: IUnlockingScript, LockingScript: ILockingScript, + + Script: IScript, PrivateKey: IPrivateKey, PublicKey: IPublicKey, Hash: IHash, Utils: IUtils, Reader: IReader, Writer: IWriter, - OP: IOP + OP: IOP, + Spend: ISpend, } \ No newline at end of file diff --git a/src/chain/base/index.ts b/src/chain/base/index.ts index 9e318b1a..728c2362 100644 --- a/src/chain/base/index.ts +++ b/src/chain/base/index.ts @@ -10,10 +10,11 @@ export * from './script/Script' export * from './script/LockingScript' export * from './script/OP' export * from './script/UnlockingScript' +export * from './script/Spend' export * from './transaction/Transaction' export * from './transaction/Broadcaster' export * from './transaction/ChainTracker' export * from './transaction/FeeModel' export * from './transaction/MerklePath' export * from './transaction/TransactionInput' -export * from './transaction/TransactionOutput' \ No newline at end of file +export * from './transaction/TransactionOutput' diff --git a/src/chain/base/primitives/PrivateKey.ts b/src/chain/base/primitives/PrivateKey.ts index a3908d5b..444dd6fc 100644 --- a/src/chain/base/primitives/PrivateKey.ts +++ b/src/chain/base/primitives/PrivateKey.ts @@ -12,7 +12,7 @@ export interface PrivateKey extends BigNumber { toAddress(prefix?: number[]): string; // eslint-disable-next-line @typescript-eslint/ban-types - sign(msg: number[] | string, enc?: 'hex' | 'utf8', forceLowS?: boolean, customK?: Function | bigint): Signature + sign(msg: number[] | string, enc?: 'hex' | 'utf8', forceLowS?: boolean, customK?: Function | BigNumber | bigint): Signature verify(msg: number[] | string, sig: Signature, enc?: 'hex'): boolean deriveSharedSecret(key: PublicKey): Point; deriveChild(publicKey: PublicKey, invoiceNumber: string): PrivateKey; diff --git a/src/chain/base/primitives/PublicKey.ts b/src/chain/base/primitives/PublicKey.ts index bb46871c..9591eef7 100644 --- a/src/chain/base/primitives/PublicKey.ts +++ b/src/chain/base/primitives/PublicKey.ts @@ -1,5 +1,4 @@ import { Point } from "./Point"; -import { PrivateKey } from "./PrivateKey"; import { Signature } from "./Signature"; @@ -8,5 +7,4 @@ export interface PublicKey extends Point { toDER(): string; toHash(enc?: 'hex'): number[] | string; toAddress(prefix: number[]): string; - deriveChild(privateKey: PrivateKey, invoiceNumber: string): PublicKey; } \ No newline at end of file diff --git a/src/chain/base/primitives/Reader.ts b/src/chain/base/primitives/Reader.ts index 05cc0d8c..9c813978 100644 --- a/src/chain/base/primitives/Reader.ts +++ b/src/chain/base/primitives/Reader.ts @@ -1,3 +1,5 @@ +import { BigNumber } from "./BigNumber"; + export interface Reader { bin: number[]; pos: number; @@ -14,13 +16,13 @@ export interface Reader { readInt32BE(): number; readUInt32LE(): number; readInt32LE(): number; - readUInt64BEBn(): bigint; + readUInt64BEBn(): BigNumber; - readUInt64LEBn(): bigint; + readUInt64LEBn(): BigNumber; readVarIntNum(): number; readVarInt(): number[]; - readVarIntBn(): bigint; + readVarIntBn(): BigNumber; } \ No newline at end of file diff --git a/src/chain/base/primitives/Signature.ts b/src/chain/base/primitives/Signature.ts index b68726bc..cb543c28 100644 --- a/src/chain/base/primitives/Signature.ts +++ b/src/chain/base/primitives/Signature.ts @@ -1,9 +1,19 @@ -import { PublicKey } from "./PublicKey"; - +import { BigNumber } from "./BigNumber"; export interface Signature { - verify(msg: number[] | string, key: PublicKey, enc?: 'hex'): boolean; + + /** + * @property Represents the "r" component of the digital signature + */ + r: BigNumber + + /** + * @property Represents the "s" component of the digital signature + */ + s: BigNumber + + toString(enc?: 'hex' | 'base64'); toDER(enc?: 'hex' | 'base64'): number[] | string; diff --git a/src/chain/base/primitives/Writer.ts b/src/chain/base/primitives/Writer.ts index 2c0d6bfc..c136b0fa 100644 --- a/src/chain/base/primitives/Writer.ts +++ b/src/chain/base/primitives/Writer.ts @@ -1,3 +1,5 @@ +import { BigNumber } from "./BigNumber"; + export interface Writer { bufs: number[][] getLength(): number; @@ -23,15 +25,15 @@ export interface Writer { writeInt32LE(n: number): Writer; - writeUInt64BEBn(bn: bigint): Writer; + writeUInt64BEBn(bn: bigint | BigNumber): Writer; - writeUInt64LEBn(bn: bigint): Writer; + writeUInt64LEBn(bn: bigint | BigNumber): Writer; writeUInt64LE(n: number): Writer; writeVarIntNum(n: number): Writer; - writeVarIntBn(bn: bigint): Writer; + writeVarIntBn(bn: bigint | BigNumber): Writer; } \ No newline at end of file diff --git a/src/chain/base/script/Script.ts b/src/chain/base/script/Script.ts index a3ed93cf..c496f7d0 100644 --- a/src/chain/base/script/Script.ts +++ b/src/chain/base/script/Script.ts @@ -1,3 +1,5 @@ +import { BigNumber } from "../primitives/BigNumber"; + export interface ScriptChunk { op: number @@ -19,7 +21,7 @@ export interface Script { writeOpCode(op: number): this; - writeBn(bn: bigint): this; + writeBn(bn: bigint | BigNumber): this; writeBin(bin: number[]): this; @@ -35,6 +37,6 @@ export interface Script { isUnlockingScript(): boolean; - setChunkOpCode(): Script; + setChunkOpCode(i: number, op: number): this; } \ No newline at end of file diff --git a/src/chain/base/script/Spend.ts b/src/chain/base/script/Spend.ts new file mode 100644 index 00000000..846f27e6 --- /dev/null +++ b/src/chain/base/script/Spend.ts @@ -0,0 +1,7 @@ +export interface Spend { + + reset(): void; + step(): void; + validate(): boolean; + +} \ No newline at end of file diff --git a/src/chain/base/transaction/Broadcaster.ts b/src/chain/base/transaction/Broadcaster.ts index d8299f38..9ca95f85 100644 --- a/src/chain/base/transaction/Broadcaster.ts +++ b/src/chain/base/transaction/Broadcaster.ts @@ -1,4 +1,4 @@ -import Transaction from './Transaction.js' +import { Transaction } from './Transaction.js' /** * Defines the structure of a successful broadcast response. @@ -38,5 +38,5 @@ export interface BroadcastFailure { */ export interface Broadcaster { broadcast: (transaction: Transaction) => - Promise + Promise } diff --git a/src/chain/base/transaction/FeeModel.ts b/src/chain/base/transaction/FeeModel.ts index 9686bd9e..a5f7452f 100644 --- a/src/chain/base/transaction/FeeModel.ts +++ b/src/chain/base/transaction/FeeModel.ts @@ -1,4 +1,4 @@ -import Transaction from './Transaction.js' +import { Transaction } from './Transaction.js' /** * Represents the interface for a transaction fee model. diff --git a/src/chain/bsv/factory.ts b/src/chain/bsv/factory.ts index 35d809f1..75601a5d 100644 --- a/src/chain/bsv/factory.ts +++ b/src/chain/bsv/factory.ts @@ -2,16 +2,18 @@ import { OP, Factory, Transaction, TransactionOutput, TransactionInput, PrivateKey, PublicKey, UnlockingScript, - LockingScript, ScriptChunk + LockingScript, ScriptChunk, + Script, Spend } from "../base"; import * as bsv from "@bsv/sdk"; -import createScriptProxy from "./script/script"; -import createTransactionProxy from "./transaction/transaction"; -import createPrivateKeyProxy from "./primitives/PrivateKey"; -import createPublicKeyProxy from "./primitives/PublicKey"; -import createReaderProxy from "./primitives/Reader"; -import createWriterProxy from "./primitives/Writer"; -import { TARGET } from "./target"; +import { createScriptProxy } from "./script/Script"; +import { createTransactionProxy } from "./transaction/Transaction"; +import { createPrivateKeyProxy } from "./primitives/PrivateKey"; +import { createPublicKeyProxy } from "./primitives/PublicKey"; +import { createReaderProxy } from "./primitives/Reader"; +import { createWriterProxy } from "./primitives/Writer"; +import { createSpendProxy } from "./script/Spend"; +import { getPreimage, num2bin, bin2num, num2asm, asm2num, signTx } from "./primitives/Utils"; @@ -81,75 +83,24 @@ export class BSVFactory implements Factory { return bsv.Utils.fromBase58Check(str, enc, prefixLength); }, getPreimage: function (tx: Transaction, subscript: LockingScript, inputAmount: number, inputIndex?: number, sighashType?: number): number[] { - - const txTarget = tx[TARGET] as bsv.Transaction; - - const subscriptTarget = subscript[TARGET] as bsv.LockingScript; - - const input = txTarget.inputs[inputIndex] - const otherInputs = txTarget.inputs.filter((_, index) => index !== inputIndex) - - const sourceTXID = input.sourceTXID || input.sourceTransaction?.id('hex') as string; - if (!sourceTXID) { - // Question: Should the library support use-cases where the source transaction is not provided? This is to say, is it ever acceptable for someone to sign an input spending some output from a transaction they have not provided? Some elements (such as the satoshi value and output script) are always required. A merkle proof is also always required, and verifying it (while also verifying that the claimed output is contained within the claimed transaction) is also always required. This seems to require the entire input transaction. - throw new Error( - 'The source transaction is needed for transaction signing.' - ) - } - - const preimage = bsv.TransactionSignature.format({ - sourceTXID: sourceTXID, - sourceOutputIndex: input.sourceOutputIndex, - sourceSatoshis: inputAmount, - transactionVersion: txTarget.version, - otherInputs, - inputIndex, - outputs: txTarget.outputs, - inputSequence: input.sequence, - subscript: subscriptTarget, - lockTime: tx.lockTime, - scope: sighashType - }) - - return preimage; + return getPreimage(tx, subscript, inputAmount, inputIndex, sighashType); }, num2bin: function (n: bigint, dataLen?: number): number[] { - const num = new bsv.BigNumber(n.toString()); - - if (typeof dataLen === 'undefined') { - return num.toSm('little'); - } - - const arr = num.toSm('little'); - - if (arr.length > dataLen) { - throw new Error(`${n} cannot fit in ${dataLen} byte[s]`); - } - - if (arr.length === dataLen) { - return arr; - } - - const paddingLen = dataLen - arr.length; - - let m = arr[arr.length - 1]; - - const rest = arr.slice(0, arr.length - 1); - if (num.isNeg()) { - // reset sign bit - m &= 0x7F; - } - - const padding = Array(paddingLen).fill(0); - if (num.isNeg()) { - padding[arr.length - 1] = 0x80 - } - return rest.concat([m]).concat(padding); + return num2bin(n, dataLen); }, bin2num: function (bin: number[]): bigint { - const bn = bsv.BigNumber.fromSm(bin, 'little'); - return BigInt(bn.toString()); + return bin2num(bin); + }, + num2asm: function (n: bigint): string { + return num2asm(n); + }, + asm2num(asm: string): bigint { + return asm2num(asm); + }, + signTx: function (tx: Transaction, privateKey: PrivateKey, subscript: LockingScript, inputAmount: number, inputIndex?: number, sighashType?: number): string { + return signTx(tx, privateKey, subscript, inputAmount, inputIndex, sighashType); } + }; PublicKey = { fromPrivateKey: function (key: PrivateKey): PublicKey { @@ -251,6 +202,53 @@ export class BSVFactory implements Factory { } } + Script = { + fromHex: function (hex: string): Script { + return createScriptProxy(bsv.Script.fromHex(hex)); + }, + fromASM: function (asm: string): Script { + return createScriptProxy(bsv.Script.fromASM(asm)); + }, + fromBinary: function (bin: number[]): Script { + return createScriptProxy(bsv.Script.fromBinary(bin)); + }, + from: function (chunks: ScriptChunk[] = []): Script { + return createScriptProxy(new bsv.Script(chunks)); + } + } + OP = OP + Spend = { + from: function (params: { + sourceTXID: string + sourceOutputIndex: number + sourceSatoshis: number + lockingScript: LockingScript + transactionVersion: number + otherInputs: TransactionInput[] + outputs: TransactionOutput[] + unlockingScript: UnlockingScript + inputSequence: number + inputIndex: number + lockTime: number + }): Spend { + + // const p = { + // sourceTXID: params.sourceTXID, + // sourceOutputIndex: params.sourceOutputIndex, + // sourceSatoshis: params.sourceSatoshis, + // lockingScript: params.lockingScript[TARGET] as bsv.LockingScript, + // transactionVersion: params.transactionVersion, + // otherInputs: params.otherInputs, + // outputs: params.outputs, + // unlockingScript: params.unlockingScript[TARGET] as bsv.UnlockingScript, + // inputSequence: params.inputSequence, + // inputIndex: params.inputIndex, + // lockTime: params.lockTime, + // } + + return createSpendProxy(new bsv.Spend(params as any)); + } + } } \ No newline at end of file diff --git a/src/chain/bsv/primitives/PrivateKey.ts b/src/chain/bsv/primitives/PrivateKey.ts index 7c5d827f..101cf226 100644 --- a/src/chain/bsv/primitives/PrivateKey.ts +++ b/src/chain/bsv/primitives/PrivateKey.ts @@ -4,7 +4,7 @@ import { PrivateKey } from "../../base/primitives/PrivateKey"; import { TARGET } from "../target"; -export default function createPrivateKeyProxy(key: bsv.PrivateKey): PrivateKey { +export function createPrivateKeyProxy(key: bsv.PrivateKey): PrivateKey { const handler = { get: function (target, prop) { @@ -13,14 +13,10 @@ export default function createPrivateKeyProxy(key: bsv.PrivateKey): PrivateKey { return target } - if (prop === 'sign') { - return () => { - } - } return Reflect.get(target, prop); } }; - return new Proxy(key, handler) as PrivateKey; + return new Proxy(key, handler); } \ No newline at end of file diff --git a/src/chain/bsv/primitives/PublicKey.ts b/src/chain/bsv/primitives/PublicKey.ts index 274c2183..95e985ac 100644 --- a/src/chain/bsv/primitives/PublicKey.ts +++ b/src/chain/bsv/primitives/PublicKey.ts @@ -4,9 +4,8 @@ import { PublicKey } from "../../base/primitives/PublicKey"; import { TARGET } from "../target"; -export default function createPublicKeyProxy(key: bsv.PublicKey): PublicKey { +export function createPublicKeyProxy(key: bsv.PublicKey): PublicKey { return new Proxy(key, { - get: function (target, prop) { if (prop === TARGET) { @@ -15,5 +14,5 @@ export default function createPublicKeyProxy(key: bsv.PublicKey): PublicKey { return Reflect.get(target, prop); } - }) as unknown as PublicKey; + }); } \ No newline at end of file diff --git a/src/chain/bsv/primitives/Reader.ts b/src/chain/bsv/primitives/Reader.ts index 5279b403..770bf5d3 100644 --- a/src/chain/bsv/primitives/Reader.ts +++ b/src/chain/bsv/primitives/Reader.ts @@ -4,14 +4,13 @@ import { Reader } from "../../base/primitives/Reader"; import { TARGET } from "../target"; -export default function createReaderProxy(reader: bsv.Utils.Reader): Reader { +export function createReaderProxy(reader: bsv.Utils.Reader): Reader { return new Proxy(reader, { - get: function (target, prop) { if (prop === TARGET) { return target } return Reflect.get(target, prop); } - }) as unknown as Reader; + }); } \ No newline at end of file diff --git a/src/chain/bsv/primitives/Signature.ts b/src/chain/bsv/primitives/Signature.ts new file mode 100644 index 00000000..a4604e17 --- /dev/null +++ b/src/chain/bsv/primitives/Signature.ts @@ -0,0 +1,19 @@ + +import { TARGET } from "../target"; +import { Signature } from "../../base/primitives/Signature"; +import * as bsv from "@bsv/sdk"; + + +export function createSignatureProxy(key: bsv.Signature): Signature { + return new Proxy(key, { + + get: function (target, prop) { + + if (prop === TARGET) { + return target + } + + return Reflect.get(target, prop); + } + }); +} \ No newline at end of file diff --git a/src/chain/bsv/primitives/Utils.ts b/src/chain/bsv/primitives/Utils.ts new file mode 100644 index 00000000..2a215f8c --- /dev/null +++ b/src/chain/bsv/primitives/Utils.ts @@ -0,0 +1,149 @@ +import { Transaction, PrivateKey, LockingScript } from "../../base"; +import * as bsv from "@bsv/sdk"; +import { TARGET } from "../target"; + + +export function toHex(bin: number[]): string { + return bsv.Utils.toHex(bin); +} + +export function getPreimage(tx: Transaction, subscript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number = 65): number[] { + + + const txTarget = tx[TARGET] as bsv.Transaction; + + const subscriptTarget = subscript[TARGET] as bsv.LockingScript; + + const input = txTarget.inputs[inputIndex] + const otherInputs = txTarget.inputs.filter((_, index) => index !== inputIndex) + + const sourceTXID = input.sourceTXID || input.sourceTransaction?.id('hex') as string; + if (!sourceTXID) { + // Question: Should the library support use-cases where the source transaction is not provided? This is to say, is it ever acceptable for someone to sign an input spending some output from a transaction they have not provided? Some elements (such as the satoshi value and output script) are always required. A merkle proof is also always required, and verifying it (while also verifying that the claimed output is contained within the claimed transaction) is also always required. This seems to require the entire input transaction. + throw new Error( + 'The source transaction is needed for transaction signing.' + ) + } + + const preimage = bsv.TransactionSignature.format({ + sourceTXID: sourceTXID, + sourceOutputIndex: input.sourceOutputIndex, + sourceSatoshis: inputAmount, + transactionVersion: txTarget.version, + otherInputs, + inputIndex, + outputs: txTarget.outputs, + inputSequence: input.sequence, + subscript: subscriptTarget, + lockTime: tx.lockTime, + scope: sighashType + }) + + return preimage; +} + +export function num2bin(n: bigint, dataLen?: number): number[] { + const num = new bsv.BigNumber(n.toString()); + + if (typeof dataLen === 'undefined') { + return num.toSm('little'); + } + + const arr = num.toSm('little'); + + if (arr.length > dataLen) { + throw new Error(`${n} cannot fit in ${dataLen} byte[s]`); + } + + if (arr.length === dataLen) { + return arr; + } + + const paddingLen = dataLen - arr.length; + + let m = arr[arr.length - 1]; + + const rest = arr.slice(0, arr.length - 1); + if (num.isNeg()) { + // reset sign bit + m &= 0x7F; + } + + const padding = Array(paddingLen).fill(0); + if (num.isNeg()) { + padding[arr.length - 1] = 0x80 + } + return rest.concat([m]).concat(padding); +} + +export function bin2num(bin: number[]): bigint { + const bn = bsv.BigNumber.fromSm(bin, 'little'); + return BigInt(bn.toString()); +} + + +export function num2asm(n: bigint): string { + const number = new bsv.BigNumber(n.toString()); + if (number.eqn(-1)) { return 'OP_1NEGATE'; } + + if (number.gten(0) && number.lten(16)) { return 'OP_' + number.toString(); } + + return number.toHex(); +} + +export function asm2num(asm: string): bigint { + switch (asm) { + case 'OP_1NEGATE': + return BigInt(-1); + case '0': + case 'OP_0': + case 'OP_1': + case 'OP_2': + case 'OP_3': + case 'OP_4': + case 'OP_5': + case 'OP_6': + case 'OP_7': + case 'OP_8': + case 'OP_9': + case 'OP_10': + case 'OP_11': + case 'OP_12': + case 'OP_13': + case 'OP_14': + case 'OP_15': + case 'OP_16': + return BigInt(asm.replace('OP_', '')); + default: { + const bn = bsv.BigNumber.fromHex(asm, 'little'); + return BigInt(bn.toString()) + } + } +} + +export function signTx(tx: Transaction, privateKey: PrivateKey, subscript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number = 65): string { + + if (!tx) { + throw new Error('param tx can not be empty'); + } + + if (!privateKey) { + throw new Error('param privateKey can not be empty'); + } + + if (!inputAmount) { + throw new Error('param inputAmount can not be empty'); + } + + const preimage = getPreimage(tx, subscript, inputAmount, inputIndex, sighashType); + + const rawSignature = privateKey.sign(bsv.Hash.sha256(preimage, 'hex')) + const sig = new bsv.TransactionSignature( + rawSignature.r as bsv.BigNumber, + rawSignature.s as bsv.BigNumber, + sighashType + ) + const sigForScript = sig.toChecksigFormat() + + return toHex(sigForScript); +} \ No newline at end of file diff --git a/src/chain/bsv/primitives/Writer.ts b/src/chain/bsv/primitives/Writer.ts index 1e5b872b..bb3b69c5 100644 --- a/src/chain/bsv/primitives/Writer.ts +++ b/src/chain/bsv/primitives/Writer.ts @@ -4,10 +4,7 @@ import { Writer } from "../../base/primitives/Writer"; import { TARGET } from "../target"; -export default function createWriterProxy(writer: bsv.Utils.Writer): Writer { - - - +export function createWriterProxy(writer: bsv.Utils.Writer): Writer { return new Proxy(writer, { get: function (target, prop) { @@ -18,5 +15,5 @@ export default function createWriterProxy(writer: bsv.Utils.Writer): Writer { return Reflect.get(target, prop); } - }) as unknown as Writer; + }); } \ No newline at end of file diff --git a/src/chain/bsv/script/Spend.ts b/src/chain/bsv/script/Spend.ts new file mode 100644 index 00000000..bba0622e --- /dev/null +++ b/src/chain/bsv/script/Spend.ts @@ -0,0 +1,19 @@ + +import * as bsv from "@bsv/sdk"; +import { Spend } from "../../base/script/Spend"; +import { TARGET } from "../target"; + +export function createSpendProxy(spend: bsv.Spend): Spend { + + return new Proxy(spend, { + // target represents the Person while prop represents + // proxy property. + get: function (target, prop) { + if (prop === TARGET) { + return target + } + + return Reflect.get(target, prop); + } + }); +} \ No newline at end of file diff --git a/src/chain/bsv/script/script.ts b/src/chain/bsv/script/script.ts index f19d9271..3cd83465 100644 --- a/src/chain/bsv/script/script.ts +++ b/src/chain/bsv/script/script.ts @@ -1,12 +1,12 @@ -import { Script as BSVScript, BigNumber, } from "@bsv/sdk"; +import * as bsv from "@bsv/sdk"; import { Script } from "../../base/script/Script"; import { TARGET } from "../target"; +import { BigNumber } from "../../base"; +export function createScriptProxy(script: bsv.Script): Script { -export default function createScriptProxy(script: BSVScript): Script { - - return new Proxy(script, { + return new Proxy(script, { // target represents the Person while prop represents // proxy property. get: function (target, prop) { @@ -14,13 +14,17 @@ export default function createScriptProxy(script: BSVScript): Script { return target } if (prop === 'writeBn') { - return function (bn: bigint) { - return Reflect.apply(target[prop], this, [new BigNumber(bn.toString())]); + return function (bn: bigint | BigNumber) { + if (typeof bn === 'bigint') { + return Reflect.apply(target[prop], this, [new bsv.BigNumber(bn.toString())]); + } else { + return Reflect.get(target, prop); + } }; } return Reflect.get(target, prop); } - }) as unknown as Script; + }); } \ No newline at end of file diff --git a/src/chain/bsv/transaction/transaction.ts b/src/chain/bsv/transaction/transaction.ts index d5140c01..aedf70ce 100644 --- a/src/chain/bsv/transaction/transaction.ts +++ b/src/chain/bsv/transaction/transaction.ts @@ -1,12 +1,12 @@ -import { Transaction as BSVTransaction } from "@bsv/sdk"; +import * as bsv from "@bsv/sdk"; import { Transaction } from "../../base/transaction/Transaction"; import { TARGET } from "../target"; -export default function createTransactionProxy(tx: BSVTransaction): Transaction { +export function createTransactionProxy(tx: bsv.Transaction): Transaction { - return new Proxy(tx, { + return new Proxy(tx, { // target represents the Person while prop represents // proxy property. get: function (target, prop) { @@ -15,6 +15,6 @@ export default function createTransactionProxy(tx: BSVTransaction): Transaction } return Reflect.get(target, prop); } - }) as unknown as Transaction; + }); } \ No newline at end of file diff --git a/src/contract.ts b/src/contract.ts index 962b8125..84714493 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -7,7 +7,7 @@ import { import { Bytes, Int, isScryptType, SupportedParamType, SymbolType, TypeInfo } from './scryptTypes'; import Stateful from './stateful'; import { arrayTypeAndSize, checkSupportedParamType, flatternArg, hasGeneric, subArrayType } from './typeCheck'; -import { LockingScript, BigNumber, OP, Script, Transaction, UnlockingScript, Chain } from './chain'; +import { LockingScript, OP, Script, Transaction, UnlockingScript, Chain } from './chain'; /** * TxContext provides some context information of the current transaction, @@ -273,12 +273,13 @@ export class AbstractContract { if (this._txContext && this._txContext.tx) { tx = typeof this._txContext.tx === 'string' ? Chain.getFactory().Transaction.fromHex(this._txContext.tx) : this._txContext.tx; } else { - const sourceTx = new Transaction(1, [], [{ + + const sourceTx = Chain.getFactory().Transaction.from(1, [], [{ lockingScript: this.lockingScript, satoshis: 100000 }], 0) - tx = new Transaction(1, [{ + tx = Chain.getFactory().Transaction.from(1, [{ sourceTransaction: sourceTx, sourceOutputIndex: 0, sequence: 0xffffffff @@ -295,7 +296,7 @@ export class AbstractContract { const sourceTXID = tx.inputs[0].sourceTXID || tx.inputs[0].sourceTransaction.id('hex'); const otherInputs = tx.inputs.filter((_, index) => index !== inputIndex) - const spend = new Spend({ + const spend = Chain.getFactory().Spend.from({ sourceTXID: sourceTXID as string, sourceOutputIndex: input.sourceOutputIndex || 0, sourceSatoshis: sourceSatoshis, @@ -796,23 +797,48 @@ export class AbstractContract { // sort the map by the result of flattenSha256 of the key private static sortmap(map: Map, keyType: string): Map { return new Map([...map.entries()].sort((a, b) => { - return unpack(this.flattenSha256(a[0], keyType)) - unpack(this.flattenSha256(a[0], keyType)); + const abn = unpack(this.flattenSha256(a[0], keyType)); + const bbn = unpack(this.flattenSha256(b[0], keyType)); + if (abn > bbn) { + return 1; + } else if (abn < bbn) { + return -1 + } else { + return 0; + } + })); } // sort the set by the result of flattenSha256 of the key private static sortset(set: Set, keyType: string): Set { return new Set([...set.keys()].sort((a, b) => { - return BigNumber.fromSm(Utils.toArray(this.flattenSha256(a, keyType)), 'little'). - cmp(BigNumber.fromSm(Utils.toArray(this.flattenSha256(b, keyType)), 'little')); + + const abn = unpack(this.flattenSha256(a[0], keyType)); + const bbn = unpack(this.flattenSha256(b[0], keyType)); + if (abn > bbn) { + return 1; + } else if (abn < bbn) { + return -1 + } else { + return 0; + } })); } private static sortkeys(keys: SupportedParamType[], keyType: string): SupportedParamType[] { return keys.sort((a, b) => { - return BigNumber.fromSm(Utils.toArray(this.flattenSha256(a, keyType)), 'little') - .cmp(BigNumber.fromSm(Utils.toArray(this.flattenSha256(b, keyType)), 'little')); + + const abn = unpack(this.flattenSha256(a[0], keyType)); + const bbn = unpack(this.flattenSha256(b[0], keyType)); + if (abn > bbn) { + return 1; + } else if (abn < bbn) { + return -1 + } else { + return 0; + } }); } diff --git a/src/deserializer.ts b/src/deserializer.ts index 703bff69..15a38f52 100644 --- a/src/deserializer.ts +++ b/src/deserializer.ts @@ -1,5 +1,5 @@ -import { Script, Utils } from '@bsv/sdk'; -import { Argument, arrayTypeAndSize, bin2num, isArrayType, LibraryEntity, ParamEntity, StructEntity, SymbolType, TypeResolver } from '.'; +import { Argument, arrayTypeAndSize, isArrayType, LibraryEntity, ParamEntity, StructEntity, SymbolType, TypeResolver } from '.'; +import { Chain } from './chain'; import { Bool, Bytes, Int, OpCodeType, PrivKey, PubKey, Ripemd160, ScryptType, Sha1, Sha256, Sig, SigHashPreimage, SigHashType, StructObject, SupportedParamType } from './scryptTypes'; import Stateful from './stateful'; @@ -14,13 +14,13 @@ export function hex2int(hex: string): bigint { } else if (hex === '4f') { return Int(-1); } else { - const b = Script.fromHex(hex); + const b = Chain.getFactory().Script.fromHex(hex); const chuck = b.chunks[0]; if (chuck.op >= 81 && chuck.op <= 96) { return BigInt(chuck.op - 80); } - return bin2num(Utils.toHex(chuck.data)); + return Chain.getFactory().Utils.bin2num(chuck.data); } } @@ -40,14 +40,14 @@ export function hex2bytes(hex: string): Bytes { return ''; } - const s = Script.fromHex(hex); + const s = Chain.getFactory().Script.fromHex(hex); const chuck = s.chunks[0]; if (chuck.op >= 81 && chuck.op <= 96) { - return Utils.toHex([chuck.op - 80]) + return Chain.getFactory().Utils.toHex([chuck.op - 80]) } - return Utils.toHex(chuck.data); + return Chain.getFactory().Utils.toHex(chuck.data); } export function deserializer(type: string, hex: string): SupportedParamType { diff --git a/src/index.ts b/src/index.ts index 953e080e..55e642ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export { } from './internal'; export { - toHex, bin2num, int2Asm, bytes2Literal, bytesToHexString, getValidatedHexString, + toHex, bin2num, getValidatedHexString, findStructByType, findStructByName, isArrayType, arrayTypeAndSize, newCall, getNameByType, genLaunchConfigFile, subArrayType, isGenericType, parseGenericType, diff --git a/src/serializer.ts b/src/serializer.ts index fbaf5f78..f85588df 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -1,25 +1,15 @@ -import { BigNumber, Script, Utils } from '@bsv/sdk'; +import { Chain } from './chain'; import { Bool, Bytes, Int, isBytes, ScryptType, SupportedParamType } from './scryptTypes'; -const BN = BigNumber - /** * int to little-endian signed magnitude */ export function int2hex(n: Int): string { - if (n === Int(0)) { - return '00'; - } else if (n === Int(-1)) { - return '4f'; - } else if (n > Int(0) && n <= Int(16)) { - n += Int(80); - return n.toString(16); - } - const number = new BN(n.toString().replace(/n/, '')); - const m = number.toSm('little'); - return Script.fromASM(Utils.toHex(m)).toHex(); + + const asm = Chain.getFactory().Utils.num2asm(n); + return Chain.getFactory().Script.fromASM(asm).toHex(); } @@ -36,7 +26,7 @@ export function bytes2hex(b: Bytes): string { if (b) { if (b.length / 2 > 1) { - return Script.fromASM(b).toHex(); + return Chain.getFactory().Script.fromASM(b).toHex(); } const intValue = parseInt(b, 16); @@ -45,7 +35,7 @@ export function bytes2hex(b: Bytes): string { return BigInt(intValue + 80).toString(16); } - return Script.fromASM(b).toHex(); + return Chain.getFactory().Script.fromASM(b).toHex(); } return '00'; } @@ -67,5 +57,5 @@ export function toScriptHex(x: SupportedParamType, type: string): string { export function toScriptASM(a: SupportedParamType, type: string): string { const hex = toScriptHex(a, type); - return Script.fromHex(hex).toASM(); + return Chain.getFactory().Script.fromHex(hex).toASM(); } diff --git a/src/stateful.ts b/src/stateful.ts index 4230a1b3..992f726b 100644 --- a/src/stateful.ts +++ b/src/stateful.ts @@ -1,7 +1,8 @@ -import { BigNumber, OP, Script, Utils } from '@bsv/sdk'; + import { AbstractContract, Arguments, bin2num, num2bin } from '.'; +import { Chain, Reader } from './chain'; import { deserializeArgfromHex } from './deserializer'; -import { flatternArg } from './internal'; +import { flatternArg, pack } from './internal'; import { Bool, Bytes, Int, isBytes, OpCodeType, PrivKey, PubKey, Ripemd160, ScryptType, Sha1, Sha256, Sig, SigHashPreimage, SigHashType, SupportedParamType, TypeResolver } from './scryptTypes'; export default class Stateful { @@ -10,20 +11,19 @@ export default class Stateful { static int2hex(n: Int): string { let asm = ''; - const num = new BigNumber(n.toString().replace(/n/, '')); - if (num.eqn(0)) { + if (n === BigInt(0)) { asm = '00'; } else { - asm = Utils.toHex(num.toSm('little')); + asm = pack(n) } - return Script.fromASM(asm).toHex(); + return Chain.getFactory().LockingScript.fromASM(asm).toHex(); } static hex2int(hex: string): bigint { - const s = Script.fromHex(hex); + const s = Chain.getFactory().LockingScript.fromHex(hex); const chuck = s.chunks[0]; - return bin2num(Utils.toHex(chuck.data)); + return bin2num(Chain.getFactory().Utils.toHex(chuck.data)); } @@ -48,16 +48,16 @@ export default class Stateful { if (b === '') { return '00'; } - return Script.fromASM(b).toHex(); + return Chain.getFactory().LockingScript.fromASM(b).toHex(); } static hex2bytes(hex: string): Bytes { if (hex === '00') { return ''; } - const s = Script.fromHex(hex); + const s = Chain.getFactory().LockingScript.fromHex(hex); const chuck = s.chunks[0]; - return Utils.toHex(chuck.data); + return Chain.getFactory().Utils.toHex(chuck.data); } static toHex(x: SupportedParamType, type: string): string { @@ -76,12 +76,7 @@ export default class Stateful { static serialize(x: SupportedParamType, type: string): string { if (type === ScryptType.INT || type === ScryptType.PRIVKEY) { - const num = new BigNumber(x.toString().replace(/n/, '')); - if (num.eqn(0)) { - return ''; - } else { - return Utils.toHex(num.toSm('little')); - } + return pack(x as bigint); } else if (type === ScryptType.BOOL) { if (x) { return '01'; @@ -204,7 +199,7 @@ export default class Stateful { - static readBytes(br: Utils.Reader): { + static readBytes(br: Reader): { data: string, opcodenum: number } { @@ -214,18 +209,18 @@ export default class Stateful { let len, data; if (opcodenum == 0) { data = ''; - } else if (opcodenum > 0 && opcodenum < OP.OP_PUSHDATA1) { + } else if (opcodenum > 0 && opcodenum < Chain.getFactory().OP.OP_PUSHDATA1) { len = opcodenum; - data = Utils.toHex(br.read(len)); - } else if (opcodenum === OP.OP_PUSHDATA1) { + data = Chain.getFactory().Utils.toHex(br.read(len)); + } else if (opcodenum === Chain.getFactory().OP.OP_PUSHDATA1) { len = br.readUInt8(); - data = Utils.toHex(br.read(len)); - } else if (opcodenum === OP.OP_PUSHDATA2) { + data = Chain.getFactory().Utils.toHex(br.read(len)); + } else if (opcodenum === Chain.getFactory().OP.OP_PUSHDATA2) { len = br.readUInt16LE(); - data = Utils.toHex(br.read(len)); - } else if (opcodenum === OP.OP_PUSHDATA4) { + data = Chain.getFactory().Utils.toHex(br.read(len)); + } else if (opcodenum === Chain.getFactory().OP.OP_PUSHDATA4) { len = br.readUInt32LE(); - data = Utils.toHex(br.read(len)); + data = Chain.getFactory().Utils.toHex(br.read(len)); } else { data = num2bin(BigInt(opcodenum - 80), 1); } @@ -250,7 +245,7 @@ export default class Stateful { const stateHex = scriptHex.substr(scriptHex.length - 10 - stateLen * 2, stateLen * 2); - const br = new Utils.Reader(Utils.toArray(stateHex, 'hex')); + const br = Chain.getFactory().Reader.from(Chain.getFactory().Utils.toArray(stateHex, 'hex')); const opcodenum = br.readUInt8(); @@ -270,7 +265,7 @@ export default class Stateful { stateTemplateArgs.set(`<${param.name}>`, opcodenum === 1 ? '01' : '00'); } else { const { data } = Stateful.readBytes(br); - stateTemplateArgs.set(`<${param.name}>`, data ? Script.fromASM(data).toHex() : '00'); + stateTemplateArgs.set(`<${param.name}>`, data ? Chain.getFactory().UnlockingScript.fromASM(data).toHex() : '00'); } }); diff --git a/src/utils.ts b/src/utils.ts index f4927ce3..8684462b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,70 +9,9 @@ import { compileAsync, OpCode } from './compilerWrapper'; import { AbstractContract, compile, CompileResult, findCompiler, getValidatedHexString, hash256, ScryptType, StructEntity, SupportedParamType, } from './internal'; import { arrayTypeAndSizeStr, isGenericType, parseGenericType } from './typeCheck'; -import { UnlockingScript, PrivateKey, LockingScript, Chain, Transaction, BigNumber, Script } from './chain' +import { PrivateKey, LockingScript, Chain, Transaction, Script } from './chain' -/** - * decimal or hex int to little-endian signed magnitude - */ -export function int2Asm(str: string): string { - - if (/^(-?\d+)$/.test(str) || /^0x([0-9a-fA-F]+)$/.test(str)) { - - const number = str.startsWith('0x') ? new BigNumber(str.substring(2), 16) : new BigNumber(str, 10); - - if (number.eqn(-1)) { return 'OP_1NEGATE'; } - - if (number.gten(0) && number.lten(16)) { return 'OP_' + number.toString(); } - - return number.toHex(); - - } else { - throw new Error(`invalid str '${str}' to convert to int`); - } -} - - - -/** - * convert asm string to number or bigint - */ -export function asm2int(str: string): number | string { - - switch (str) { - case 'OP_1NEGATE': - return -1; - case '0': - case 'OP_0': - case 'OP_1': - case 'OP_2': - case 'OP_3': - case 'OP_4': - case 'OP_5': - case 'OP_6': - case 'OP_7': - case 'OP_8': - case 'OP_9': - case 'OP_10': - case 'OP_11': - case 'OP_12': - case 'OP_13': - case 'OP_14': - case 'OP_15': - case 'OP_16': - return parseInt(str.replace('OP_', '')); - default: { - const value = getValidatedHexString(str); - const bn = BigNumber.fromHex(value, 'little'); - - if (bn.toNumber() < Number.MAX_SAFE_INTEGER && bn.toNumber() > Number.MIN_SAFE_INTEGER) { - return bn.toNumber(); - } else { - return bn.toString(); - } - } - } -} @@ -108,30 +47,6 @@ export function utf82Hex(val: string): string { - -export function bytes2Literal(bytearray: Buffer, type: string): string { - - switch (type) { - case 'bool': - return Array.from(bytearray)[0] !== 0 ? 'true' : 'false'; - - case 'int': - case 'PrivKey': - return BigNumber.fromSm(Array.from(bytearray), "little").toString(); - - case 'bytes': - return `b'${bytesToHexString(bytearray)}'`; - - default: - return `b'${bytesToHexString(bytearray)}'`; - } - -} - -export function bytesToHexString(bytearray: Buffer): string { - return bytearray.reduce(function (o, c) { return o += ('0' + (c & 0xFF).toString(16)).slice(-2); }, ''); -} - export function hexStringToBytes(hex: string): number[] { getValidatedHexString(hex); @@ -149,31 +64,8 @@ export function hexStringToBytes(hex: string): number[] { } -export function signTx(tx: Transaction, privateKey: PrivateKey, subscript: LockingScript, inputAmount: number, inputIndex = 0, sighashType: number = 65): string { - - if (!tx) { - throw new Error('param tx can not be empty'); - } - - if (!privateKey) { - throw new Error('param privateKey can not be empty'); - } - - if (!inputAmount) { - throw new Error('param inputAmount can not be empty'); - } - - const preimage = getPreimage(tx, subscript, inputAmount, inputIndex, sighashType); - - const rawSignature = privateKey.sign(Hash.sha256(preimage, 'hex')) - const sig = new TransactionSignature( - rawSignature.r, - rawSignature.s, - sighashType - ) - const sigForScript = sig.toChecksigFormat() - - return toHex(sigForScript); +export function signTx(tx: Transaction, privateKey: PrivateKey, subscript: LockingScript, inputAmount: number, inputIndex?: number, sighashType?: number): string { + return Chain.getFactory().Utils.signTx(tx, privateKey, subscript, inputAmount, inputIndex, sighashType) } @@ -196,7 +88,7 @@ export function getLowSPreimage(tx: Transaction, lockingScript: LockingScript, i for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) { const preimage = getPreimage(tx, lockingScript, inputAmount, inputIndex, sighashType); - const sighash = hash256(preimage); + const sighash = toArray(hash256(preimage)); const msb = sighash[0] if (msb < MSB_THRESHOLD && hashIsPositiveNumber(sighash)) { return preimage; @@ -482,10 +374,9 @@ export function parseAbiFromUnlockingScript(contract: AbstractContract, hex: str const pubFuncIndexASM = usASM.substr(usASM.lastIndexOf(' ') + 1); - const pubFuncIndex = asm2int(pubFuncIndexASM); - + const pubFuncIndex = Chain.getFactory().Utils.asm2num(pubFuncIndexASM); - const entity = abis.find(entity => entity.index === pubFuncIndex); + const entity = abis.find(entity => entity.index === Number(pubFuncIndex)); if (!entity) { throw new Error(`the raw unlocking script cannot match the contract ${contract.contractName}`); @@ -603,7 +494,7 @@ export function checkNOPScript(nopScript: Script) { } -export function addScript(a: Script, b: Script): Script { +export function addScript(a: LockingScript, b: LockingScript): LockingScript { const merged = a.chunks.concat(b.chunks); - return new Script(merged); + return Chain.getFactory().LockingScript.from(merged); } \ No newline at end of file diff --git a/test/abi.test.ts b/test/abi.test.ts index 7155f120..fce234f2 100644 --- a/test/abi.test.ts +++ b/test/abi.test.ts @@ -4,9 +4,9 @@ import { FunctionCall } from '../src/abi'; import { buildContractClass, VerifyResult } from '../src/contract'; import { signTx, toHex } from '../src/utils'; import { PubKey, Sig, Ripemd160, Sha256 } from '../src/scryptTypes'; -import { PrivateKey, Script } from '@bsv/sdk'; +import { Chain } from '../src/chain'; -const privateKey = PrivateKey.fromRandom(); +const privateKey = Chain.getFactory().PrivateKey.fromRandom(); const publicKey = privateKey.toPublicKey() const pubKeyHash = publicKey.toHash('hex') as string const inputSatoshis = 100000; @@ -75,7 +75,7 @@ describe('FunctionCall', () => { sig = Sig(signTx(tx, privateKey, p2pkh.lockingScript, inputSatoshis)); pubkey = PubKey(toHex(publicKey)); target = new FunctionCall('unlock', { - contract: p2pkh, unlockingScript: Script.fromASM([sig, pubkey].join(' ')), args: [{ + contract: p2pkh, unlockingScript: Chain.getFactory().UnlockingScript.fromASM([sig, pubkey].join(' ')), args: [{ name: 'sig', type: 'Sig', value: sig @@ -89,7 +89,7 @@ describe('FunctionCall', () => { describe('toHex() / toString()', () => { it('should return the unlocking script in hex', () => { - assert.equal(target.toHex(), Script.fromASM(target.toASM()).toHex()); + assert.equal(target.toHex(), Chain.getFactory().UnlockingScript.fromASM(target.toASM()).toHex()); }) }) diff --git a/test/helper.ts b/test/helper.ts index e2be9ed0..bb68329b 100644 --- a/test/helper.ts +++ b/test/helper.ts @@ -1,7 +1,8 @@ import { join } from 'path'; import { readFileSync, existsSync } from 'fs'; import { Artifact } from '../src/contract'; -import { LockingScript, Transaction, UnlockingScript } from '@bsv/sdk'; +import { Chain, LockingScript } from '../src/chain'; + export function loadArtifact(fileName: string): Artifact { return JSON.parse(readFileSync(join(__dirname, "../out/", fileName)).toString()); } @@ -18,14 +19,14 @@ export function getInvalidContractFilePath(fileName: string): string { return join(__dirname, 'fixture', 'invalid', fileName); } -export function newTx(inputSatoshis: number = 100000, lockingScript: LockingScript = new LockingScript()) { +export function newTx(inputSatoshis: number = 100000, lockingScript: LockingScript = Chain.getFactory().LockingScript.from()) { - const sourceTx = new Transaction(1, [], [{ + const sourceTx = Chain.getFactory().Transaction.from(1, [], [{ lockingScript: lockingScript, satoshis: inputSatoshis }], 0) - const spendTx = new Transaction(1, [{ + const spendTx = Chain.getFactory().Transaction.from(1, [{ sourceTransaction: sourceTx, sourceOutputIndex: 0, sequence: 0xffffffff, diff --git a/test/tx.test.ts b/test/tx.test.ts index 45b6960b..859e305c 100644 --- a/test/tx.test.ts +++ b/test/tx.test.ts @@ -2,7 +2,7 @@ import { OP } from "@bsv/sdk"; import { Chain } from "../src/chain/chain"; -const factory = Chain.getFactory(Chain.BSV); +const factory = Chain.getFactory(); const s = factory.LockingScript.fromHex('76a914212771cc264264057238cc3b98a03ddd9aa3a31c88ac'); diff --git a/test/utils.test.ts b/test/utils.test.ts index 4dbbc0ee..a2b824e8 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import { buildContractClass } from '../src/contract'; import { Int, Bool, Bytes, PrivKey, Ripemd160, PubKey, SymbolType, TypeResolver, Sig } from '../src/scryptTypes' import { - num2bin, bin2num, bsv, int2Asm, arrayTypeAndSize, + num2bin, bin2num, arrayTypeAndSize, parseGenericType, isArrayType, compileContract, isGenericType, sha256, hash256, hash160, @@ -138,33 +138,6 @@ describe('utils', () => { }) - describe('int2Asm()', () => { - - it('int string to asm', () => { - expect(int2Asm("992218700866541488854030164190743727617658394826382323005192752278160641622424126616186015754450906117445668830393086070718237548341612508577988597572812")) - .to.equal("cce42011b595b8ef7742710a4492a130e4b7e020097044e7b86258f82ae25f0467e8a0141ae5afd7038810f692f52d43fbb03363b8320d3b43dc65092eddf112") - - - expect(int2Asm("0x12f1dd2e0965dc433b0d32b86333b0fb432df592f6108803d7afe51a14a0e867045fe22af85862b8e744700920e0b7e430a192440a714277efb895b51120e4cc")) - .to.equal("cce42011b595b8ef7742710a4492a130e4b7e020097044e7b86258f82ae25f0467e8a0141ae5afd7038810f692f52d43fbb03363b8320d3b43dc65092eddf112") - - expect(int2Asm("-1")) - .to.equal("OP_1NEGATE") - - expect(int2Asm("0")) - .to.equal("OP_0") - - - expect(int2Asm("1")) - .to.equal("OP_1") - - expect(int2Asm("-2")) - .to.equal("82") - }); - }) - - - describe('parseLiteral()', () => { it('parser Literal string', () => {