-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #332 from yusufidimaina9989/master
Add caller contract
- Loading branch information
Showing
2 changed files
with
264 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |