Skip to content

Commit

Permalink
Merge pull request #332 from yusufidimaina9989/master
Browse files Browse the repository at this point in the history
Add caller contract
  • Loading branch information
zhfnjust authored Mar 27, 2024
2 parents 8f642ca + 91ee226 commit df6cc45
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 0 deletions.
73 changes: 73 additions & 0 deletions src/contracts/caller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* a contract calling quadratic equation contract
* https://xiaohuiliu.medium.com/inter-contract-call-on-bitcoin-f51869c08be
*/

import {
Addr,
ByteString,
SmartContract,
Utils,
assert,
byteString2Int,
hash256,
len,
method,
prop,
slice,
} from 'scrypt-ts'
import { Coeff } from './callee'
import { TxUtil } from './txUtil'


export class Caller extends SmartContract {
@prop()
static readonly N: bigint = 2n
@prop()
static readonly calleeContractInputIndex: bigint = 1n
// hash of the callee contract, i.e., its locking script
@prop()
calleeContractHash: Addr

constructor(calleeContractHash: Addr) {
super(...arguments)
this.calleeContractHash = calleeContractHash
}

@method()
public calls(
co: Coeff,
prevouts: ByteString,
calleeContractTx: ByteString,
outputScript: ByteString,
amount: bigint
) {
assert(
TxUtil.verifyContractByHash(
prevouts,
Caller.calleeContractInputIndex,
calleeContractTx,
this.calleeContractHash
)
)

let l = len(outputScript)
const a: bigint = byteString2Int(
slice(outputScript, l - 4n * Caller.N, l - 3n * Caller.N)
)
const b: bigint = byteString2Int(
slice(outputScript, l - 3n * Caller.N, l - 2n * Caller.N)
)
const c: bigint = byteString2Int(
slice(outputScript, l - 2n * Caller.N, l - Caller.N)
)
assert(co.a === a && co.b === b && co.c === c)
const x: bigint = byteString2Int(slice(outputScript, l - Caller.N))
// ------>> x must be a root for the following quadatic equition: no need to double check
// require(a * x * x + b * x + c == 0);

const output: ByteString = Utils.buildOutput(outputScript, amount)
assert(hash256(output) == this.ctx.hashOutputs)
}
}
191 changes: 191 additions & 0 deletions src/contracts/txUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// forked from https://sensiblecontract.org

import {
Addr,
ByteString,
Constants,
SmartContractLib,
Utils,
assert,
hash160,
hash256,
method,
slice,
toByteString,
} from 'scrypt-ts'

type Output = {
satoshis: bigint
script: ByteString
}

export class TxUtil extends SmartContractLib {
@method()
static readOutput(tx: ByteString, outputIndex: bigint): Output {
// first 4 bytes version
// 1 byte input num, only support max 3
let pos: bigint = 4n
let ninputs: bigint = Utils.fromLEUnsigned(slice(tx, pos, pos + 1n))
pos = pos + 1n
let script: ByteString = toByteString('')
let satoshis: bigint = 0n
// max support 3 input
// input
assert(ninputs <= 3n)
for (let i = 0; i < 3; i++) {
if (i < ninputs) {
// output point 36 bytes
pos = pos + 36n
// 1 byte var
// script code + 4 bytes sequence
let varLen: bigint = Utils.fromLEUnsigned(
slice(tx, pos, pos + 1n)
)
if (varLen < 253) {
let scriptLen: bigint = varLen
pos = pos + 1n + scriptLen + 4n
} else if (varLen == 253n) {
let scriptLen: bigint = Utils.fromLEUnsigned(
slice(tx, pos + 1n, pos + 3n)
)
pos = pos + 3n + scriptLen + 4n
} else if (varLen == 254n) {
let scriptLen: bigint = Utils.fromLEUnsigned(
slice(tx, pos + 1n, pos + 5n)
)
pos = pos + 5n + scriptLen + 4n
} else {
let scriptLen: bigint = Utils.fromLEUnsigned(
slice(tx, pos + 1n, pos + 9n)
)
pos = pos + 9n + scriptLen + 4n
}
}
}

let noutputs: bigint = Utils.fromLEUnsigned(slice(tx, pos, pos + 1n))
pos = pos + 1n
assert(noutputs <= 3n)
for (let i = 0n; i < 3n; i++) {
if (i < noutputs) {
// 8 bytes value
let sats: bigint = Utils.fromLEUnsigned(
slice(tx, pos, pos + 8n)
)
pos = pos + 8n
// script code
let varLen: bigint = Utils.fromLEUnsigned(
slice(tx, pos, pos + 1n)
)
let scriptLen: bigint = 0n
if (varLen < 253n) {
scriptLen = varLen
pos = pos + 1n + scriptLen
} else if (varLen == 253n) {
scriptLen = Utils.fromLEUnsigned(
slice(tx, pos + 1n, pos + 3n)
)
pos = pos + 3n + scriptLen
} else if (varLen == 254n) {
scriptLen = Utils.fromLEUnsigned(
slice(tx, pos + 1n, pos + 5n)
)
pos = pos + 5n + scriptLen
} else {
scriptLen = Utils.fromLEUnsigned(
slice(tx, pos + 1n, pos + 9n)
)
pos = pos + 9n + scriptLen
}
if (i == outputIndex) {
script = slice(tx, pos - scriptLen, pos)
satoshis = sats
}
}
}

// 4 bytes locktime
return { satoshis, script }
}

@method()
static getScriptCodeFromOutput(output: ByteString): ByteString {
return Utils.readVarint(slice(output, 8n))
}

@method()
static getVarOpLen(length: bigint): bigint {
let res: bigint = 0n
if (length <= 75n) {
res = 1n
} else if (length <= 255n) {
res = 2n
} else if (length <= 65535n) {
res = 3n
} else {
res = 5n
}
return res
}

@method()
static getVarOpLenOpt(length: bigint): bigint {
let res: bigint = 0n
if (length <= 75n) {
res = 1n
} else {
res = 2n
}
return res
}

@method()
static genBsvOutput(satoshis: bigint, address: Addr): ByteString {
let output = toByteString('')
if (satoshis > 0n) {
let outputScript = Utils.buildPublicKeyHashScript(address)
output = Utils.buildOutput(outputScript, satoshis)
}
return output
}

// get i-th outpoint's txid
@method()
static getPrevoutTxid(prevouts: ByteString, i: bigint): ByteString {
let offset: bigint = i * Constants.OutpointLen
return slice(prevouts, offset, offset + Constants.TxIdLen)
}

// get i-th outpoint's output index
@method()
static getPrevoutOutputIdx(prevouts: ByteString, i: bigint): bigint {
let offset = i * Constants.OutpointLen
return Utils.fromLEUnsigned(
slice(
prevouts,
offset + Constants.TxIdLen,
offset + Constants.OutpointLen
)
)
}

// verify a contract, called in another input, by its hash
@method()
static verifyContractByHash(
prevouts: ByteString,
inputIdx: bigint,
prevTx: ByteString,
contractScriptHash: Addr
): boolean {
// validate the tx containing the called contract
const prevTxId = TxUtil.getPrevoutTxid(prevouts, inputIdx)
assert(hash256(prevTx) == prevTxId)

// validate the called contract, i.e., its locking script
const outputIndex = TxUtil.getPrevoutOutputIdx(prevouts, inputIdx)
const contractScript = TxUtil.readOutput(prevTx, outputIndex).script
assert(Addr(contractScript) == contractScriptHash)

return true
}
}

0 comments on commit df6cc45

Please sign in to comment.