├── test ├── __init__.py ├── test_utils.py ├── res │ ├── desc │ │ ├── secp256k1_desc.json.gz │ │ └── P2GenericECDSAPubKey_desc.json.gz │ ├── equals.scrypt │ ├── multiSig.scrypt │ ├── p2pkh.scrypt │ ├── ecAddition.scrypt │ ├── pay2decrypt.scrypt │ ├── clone.scrypt │ ├── demo.scrypt │ ├── rabin.scrypt │ ├── hashpuzzle.scrypt │ ├── p2sh.scrypt │ ├── rpuzzle.scrypt │ ├── optimalPushtx.scrypt │ ├── statecounter.scrypt │ ├── ecdsaCheckSig.scrypt │ ├── P2GenericECDSAPubKey.scrypt │ ├── modExp.scrypt │ ├── acs.scrypt │ ├── schnorr.scrypt │ ├── counter.scrypt │ ├── testMerkleTree.scrypt │ ├── alias.scrypt │ ├── dynamicArrayDemo.scrypt │ ├── tokenSale.scrypt │ ├── sighash.scrypt │ ├── oracle.scrypt │ ├── dynamicArray.scrypt │ ├── vanityAddr.scrypt │ ├── stateSet.scrypt │ ├── ackermann.scrypt │ ├── token.scrypt │ ├── structdemo.scrypt │ ├── asm.scrypt │ ├── stateStruct.scrypt │ ├── advancedCounter.scrypt │ ├── stateMap.scrypt │ ├── matrix.scrypt │ ├── tokenManualState.scrypt │ ├── ecdsa.scrypt │ ├── advancedTokenSale.scrypt │ ├── deadMansSwitch.scrypt │ ├── escrow.scrypt │ ├── conwaygol.scrypt │ ├── nonFungibleToken.scrypt │ ├── faucet.scrypt │ ├── rps.scrypt │ ├── merkleTree.scrypt │ ├── ecVerify.scrypt │ ├── merkleToken.scrypt │ ├── tokenUtxo.scrypt │ ├── serializer.scrypt │ └── arraydemo.scrypt ├── test_contract_demo.py ├── test_contract_dynamic_array_demo.py ├── test_contract_ackermann.py ├── test_contract_equals.py ├── test_contract_simpleBVM.py ├── test_contract_hashpuzzle.py ├── test_optimized_compilation.py ├── test_multi_contract.py ├── test_contract_clone.py ├── test_contract_optimal_push_tx.py ├── test_contract_mod_exp.py ├── test_contract_p2sh.py ├── test_contract_p2pkh.py ├── test_contract_token_sale.py ├── test_contract_matrix.py ├── test_contract_state_struct.py ├── test_contract_rabin.py ├── test_contract_alias.py ├── test_contract_asm.py ├── test_contract_state_counter.py ├── test_contract_rpuzzle.py ├── test_contract_multisig.py ├── test_contract_conwaygol.py ├── test_contract_p2generic_ecdsa_pubkey.py ├── test_contract_token_manual_state.py ├── test_contract_nested_struct_arr.py ├── test_contract_state_map.py ├── test_contract_advanced_counter.py ├── test_contract_serializer.py ├── test_contract_advanced_token_sale.py ├── test_contract_merkle_token.py ├── test_contract_acs.py ├── test_contract_counter.py ├── test_contract_token.py ├── test_contract_secp256k1.py ├── test_contract_faucet.py ├── test_contract_rps.py ├── test_types.py ├── test_contract_non_fungible_token.py ├── test_contract_escrow.py ├── test_contract_structdemo.py └── test_contract_arraydemo.py ├── requirements.txt ├── scryptlib ├── __init__.py └── serializer.py ├── .travis.yml ├── LICENSE ├── setup.py ├── examples └── broadcast_contract_p2pkh.py ├── .gitignore └── README.md /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_utils.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | . 2 | -------------------------------------------------------------------------------- /test/res/desc/secp256k1_desc.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sCrypt-Inc/py-scryptlib/HEAD/test/res/desc/secp256k1_desc.json.gz -------------------------------------------------------------------------------- /test/res/desc/P2GenericECDSAPubKey_desc.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sCrypt-Inc/py-scryptlib/HEAD/test/res/desc/P2GenericECDSAPubKey_desc.json.gz -------------------------------------------------------------------------------- /scryptlib/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.4.7' 2 | 3 | 4 | from .utils import * 5 | from .types import * 6 | from .compiler_wrapper import * 7 | from .contract import * 8 | from .abi import * 9 | from .serializer import * 10 | 11 | -------------------------------------------------------------------------------- /test/res/equals.scrypt: -------------------------------------------------------------------------------- 1 | contract Equals { 2 | int x; 3 | 4 | constructor(int x) { 5 | this.x = x; 6 | } 7 | 8 | public function equals(int y) { 9 | require(this.x == y); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /test/res/multiSig.scrypt: -------------------------------------------------------------------------------- 1 | contract MultiSig { 2 | Ripemd160[N] pubKeyHashs; 3 | static const int N = 3; // total of keys 4 | public function unlock(PubKey[N] pubKeys, Sig[N] sigs) { 5 | require(checkMultiSig(sigs, pubKeys)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/res/p2pkh.scrypt: -------------------------------------------------------------------------------- 1 | contract DemoP2PKH { 2 | Ripemd160 pubKeyHash; 3 | 4 | constructor(PubKeyHash pubKeyHash) { 5 | this.pubKeyHash = pubKeyHash; 6 | } 7 | 8 | public function unlock(Sig sig, PubKey pubKey) { 9 | require(hash160(pubKey) == this.pubKeyHash); 10 | require(checkSig(sig, pubKey)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/res/ecAddition.scrypt: -------------------------------------------------------------------------------- 1 | import "ecVerify.scrypt"; 2 | 3 | contract ECAddition { 4 | Point p1; 5 | Point p2; 6 | 7 | // p must be equal to p1 + p2 8 | // lambda must be the gradient of the line between p1 and p2 9 | public function testSum(int lambda, Point p) { 10 | require(ECVerify.isSum(this.p1, this.p2, lambda, p)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/res/pay2decrypt.scrypt: -------------------------------------------------------------------------------- 1 | import "ec.scrypt"; 2 | 3 | contract Pay2Decrypt { 4 | // ciphertext: (K, C) 5 | Point K; 6 | Point C; 7 | 8 | public function decrypt(PrivKey privKey, Point plaintext) { 9 | Point sharedSecret = EC.multByScalar(this.K, privKey); 10 | require(EC.addPoints(sharedSecret, plaintext) == this.C); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/res/clone.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | contract Clone { 4 | 5 | public function unlock(SigHashPreimage txPreimage) { 6 | require(Tx.checkPreimage(txPreimage)); 7 | 8 | bytes output = Util.buildOutput(Util.scriptCode(txPreimage), Util.value(txPreimage)); 9 | require(hash256(output) == Util.hashOutputs(txPreimage)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | language: python 3 | sudo: required 4 | python: 5 | - "3.7" 6 | - "3.8" 7 | - "3.9" 8 | - "3.9-dev" # 3.9 development branch 9 | - "nightly" # nightly build 10 | install: 11 | - pip install -r requirements.txt 12 | - pip install ".[testing]" 13 | script: 14 | - curl -Ls https://scrypt.io/setup | sudo sh -s -- -f -v 1.16.0 15 | - cd /home/travis/build/sCrypt-Inc/py-scryptlib 16 | - pytest 17 | -------------------------------------------------------------------------------- /test/res/demo.scrypt: -------------------------------------------------------------------------------- 1 | contract Demo { 2 | int x; 3 | int y; 4 | 5 | constructor(int x, int y) { 6 | this.x = x; 7 | this.y = y; 8 | } 9 | 10 | function sum(int a, int b) : int { 11 | return a + b; 12 | } 13 | 14 | public function add(int z) { 15 | require(z == this.sum(this.x, this.y)); 16 | } 17 | 18 | public function sub(int z) { 19 | require(z == this.x - this.y); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/res/rabin.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | contract RabinSignature { 4 | public function verifySig(int sig, bytes msg, bytes padding, int n) { 5 | int h = Util.fromLEUnsigned(this.hash(msg + padding)); 6 | require((sig * sig) % n == h % n); 7 | } 8 | 9 | function hash(bytes x) : bytes { 10 | // expand into 512 bit hash 11 | bytes hx = sha256(x); 12 | int idx = len(hx) / 2; 13 | return sha256(hx[: idx]) + sha256(hx[idx :]); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/res/hashpuzzle.scrypt: -------------------------------------------------------------------------------- 1 | /** 2 | A Bitcoin contract which is instantiated with a shasum of known data 3 | required to be input to spend the output. 4 | Demo values: 5 | sha256("abc") = 0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 6 | bytes("abc") = 0x616263 7 | **/ 8 | 9 | // Main Data Check 10 | contract HashPuzzle { 11 | Sha256 hash; 12 | 13 | // Main function verifies the data matches the provided Sha256Sum 14 | public function verify(bytes data) { 15 | require(sha256(data) == this.hash); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/res/p2sh.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | contract P2SH { 4 | Ripemd160 scriptHash; 5 | 6 | public function redeem(bytes redeemScript, SigHashPreimage txPreimage) { 7 | require(Tx.checkPreimage(txPreimage)); 8 | 9 | require(hash160(redeemScript) == this.scriptHash); 10 | 11 | // use redeem script as the new locking script, while maintaining value 12 | bytes output = Util.buildOutput(redeemScript, Util.value(txPreimage)); 13 | require(hash256(output) == Util.hashOutputs(txPreimage)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/res/rpuzzle.scrypt: -------------------------------------------------------------------------------- 1 | contract RPuzzle { 2 | Ripemd160 rhash; 3 | 4 | constructor(Ripemd160 rhash) { 5 | this.rhash = rhash; 6 | } 7 | 8 | function getSigR(Sig sigr) : bytes { 9 | bytes lenBytes = sigr[3 : 4]; 10 | int len = unpack(lenBytes); 11 | bytes r = sigr[4 : 4 + len]; 12 | return r; 13 | } 14 | 15 | public function unlock(Sig sig, PubKey pubKey, Sig sigr) { 16 | require(this.rhash == hash160(this.getSigR(sigr))); 17 | require(checkSig(sigr, pubKey)); 18 | require(checkSig(sig, pubKey)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/test_contract_demo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | 6 | 7 | contract = './test/res/demo.scrypt' 8 | 9 | compiler_result = scryptlib.utils.compile_contract(contract) 10 | desc = compiler_result.to_desc() 11 | 12 | Demo = scryptlib.contract.build_contract_class(desc) 13 | demo = Demo(7, 4) 14 | 15 | 16 | def test_verify_correct(): 17 | verify_result = demo.add(7 + 4).verify() 18 | assert verify_result == True 19 | 20 | 21 | def test_verify_wrong(): 22 | verify_result = demo.add(7 - 4).verify() 23 | assert verify_result == False 24 | 25 | -------------------------------------------------------------------------------- /test/res/optimalPushtx.scrypt: -------------------------------------------------------------------------------- 1 | /** 2 | * test contract to compare OP_PUSH_TX with and without optimization 3 | */ 4 | contract OptimalPushTx { 5 | public function validate(SigHashPreimage txPreimage) { 6 | // compare the output script size of the following two to see effect of optimization 7 | 8 | // 633 bytes 9 | // require(Tx.checkPreimage(txPreimage)); 10 | 11 | // 92 bytes 12 | require(Tx.checkPreimageOpt(txPreimage)); 13 | 14 | // use this if you want to set sighash type explicitly 15 | // require(Tx.checkPreimageOpt_(txPreimage)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/res/statecounter.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | contract StateCounter { 3 | 4 | @state 5 | int counter; 6 | 7 | //constructor(int counter) { 8 | // this.counter = counter; 9 | //} 10 | 11 | public function unlock(SigHashPreimage txPreimage, int amount) { 12 | require(Tx.checkPreimage(txPreimage)); 13 | // increment counter 14 | this.counter++; 15 | bytes outputScript = this.getStateScript(); 16 | bytes output = Util.buildOutput(outputScript, amount); 17 | require(hash256(output) == Util.hashOutputs(txPreimage)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/test_contract_dynamic_array_demo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import SigHashPreimage, Int 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, TxOutput, Script 9 | 10 | 11 | contract = './test/res/dynamicArrayDemo.scrypt' 12 | 13 | compiler_result = scryptlib.utils.compile_contract(contract) 14 | desc = compiler_result.to_desc() 15 | 16 | Demo = scryptlib.contract.build_contract_class(desc) 17 | demo = Demo() 18 | 19 | 20 | def test_verify_correct(): 21 | verify_result = demo.test(0).verify() 22 | assert verify_result == True 23 | 24 | -------------------------------------------------------------------------------- /test/res/ecdsaCheckSig.scrypt: -------------------------------------------------------------------------------- 1 | import "ecArithmeticOptimized.scrypt" 2 | 3 | contract TestCheckSig { 4 | 5 | public function testAdd(Point a, Point b, Point sum) { 6 | require(ECDSA.addPoints(a, b) == sum); 7 | } 8 | 9 | public function testDouble(Point a, Point d) { 10 | require(ECDSA.doublePoint(a) == d); 11 | } 12 | 13 | public function testMultByScalar(Point p, int scalar, Point res) { 14 | require(ECDSA.multByScalar(p, scalar) == res); 15 | } 16 | 17 | public function testVerifySig(bytes m, Signature sig, Point pubKey) { 18 | require(ECDSA.verifySig(m, sig, pubKey) == true); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /test/res/P2GenericECDSAPubKey.scrypt: -------------------------------------------------------------------------------- 1 | import "ec.scrypt"; 2 | 3 | 4 | contract P2GPK { 5 | 6 | Point pk; 7 | 8 | public function unlock(int e, int z, SigHashPreimage preimage) { 9 | require(Tx.checkPreimage(preimage)); 10 | 11 | // Compute A = z*G - e*PK 12 | Point zG = EC.multByScalar(EC.G, z); 13 | Point ePK = EC.multByScalar(this.pk, e); 14 | Point A = EC.addPoints(zG, EC.negatePoint(ePK)); 15 | 16 | // Compute e* = RO(preimage, st, A) 17 | bytes st = EC.point2PubKey(EC.G) + EC.point2PubKey(this.pk); 18 | bytes bA = EC.point2PubKey(A); 19 | int _e = unpack(sha256(preimage + st + bA) + b'00') % 340282366920938463463374607431768211456; 20 | 21 | require(e == _e); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /test/res/modExp.scrypt: -------------------------------------------------------------------------------- 1 | contract ModExp { 2 | // max # of bits for e = ceil(log2(y)) 3 | static const int N = 232; 4 | // modulus 5 | int M; 6 | 7 | // x^y % M 8 | function modExp(int x, int y) : int { 9 | int res = 1; 10 | x = x % this.M; 11 | 12 | if (x != 0) { 13 | loop (N) { 14 | if (y >= 0) { 15 | // If y is odd, multiply x with result 16 | if (y % 2) res = (res * x) % this.M; 17 | 18 | // y >> 1 19 | y = y / 2; 20 | x = (x * x) % this.M; 21 | } 22 | } 23 | } 24 | else { 25 | res = 0; 26 | } 27 | 28 | return res; 29 | } 30 | 31 | public function main(int x, int y, int z) { 32 | require(z == this.modExp(x, y)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/test_contract_ackermann.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, Ripemd160 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, PrivateKey, pack_byte 9 | 10 | 11 | contract = './test/res/ackermann.scrypt' 12 | 13 | compiler_result = scryptlib.utils.compile_contract(contract) 14 | desc = compiler_result.to_desc() 15 | 16 | Ackermann = scryptlib.contract.build_contract_class(desc) 17 | ackermann = Ackermann(2, 1) 18 | 19 | 20 | def test_verify_correct(): 21 | verify_result = ackermann.unlock(5).verify() 22 | assert verify_result == True 23 | 24 | 25 | def test_verify_wrong_1(): 26 | verify_result = ackermann.unlock(6).verify() 27 | assert verify_result == False 28 | 29 | 30 | def test_verify_wrong_2(): 31 | verify_result = ackermann.unlock(-1).verify() 32 | assert verify_result == False 33 | -------------------------------------------------------------------------------- /test/test_contract_equals.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int 6 | 7 | 8 | EQUAL_VAL = 1232326327186381 9 | 10 | contract = './test/res/equals.scrypt' 11 | 12 | compiler_result = scryptlib.utils.compile_contract(contract) 13 | desc = compiler_result.to_desc() 14 | 15 | Equals = scryptlib.contract.build_contract_class(desc) 16 | contract_obj = Equals(Int(EQUAL_VAL)) 17 | 18 | 19 | def test_verify_correct(): 20 | verify_result = contract_obj.equals(Int(EQUAL_VAL)).verify() 21 | assert verify_result == True 22 | 23 | 24 | def test_verify_incorrect0(): 25 | verify_result = contract_obj.equals(Int(342768423)).verify() 26 | assert verify_result == False 27 | 28 | 29 | def test_verify_incorrect1(): 30 | verify_result = contract_obj.equals(Int(-1232326327186381)).verify() 31 | assert verify_result == False 32 | 33 | -------------------------------------------------------------------------------- /test/test_contract_simpleBVM.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage, Bytes 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, sha256 9 | 10 | 11 | input_script = '525593569357936094539354935894' 12 | 13 | contract = './test/res/simpleBVM.scrypt' 14 | 15 | compiler_result = scryptlib.utils.compile_contract(contract) 16 | desc = compiler_result.to_desc() 17 | 18 | SimpleBVM = scryptlib.contract.build_contract_class(desc) 19 | simple_BVM = SimpleBVM(3) 20 | 21 | 22 | def test_verify_correct(): 23 | verify_result = simple_BVM.unlock(Bytes(input_script)).verify() 24 | assert verify_result == True 25 | 26 | 27 | def test_verify_incorrect(): 28 | with pytest.raises(bitcoinx.VerifyFailed): 29 | simple_BVM.unlock(Bytes(input_script + '52')).verify() 30 | -------------------------------------------------------------------------------- /test/res/acs.scrypt: -------------------------------------------------------------------------------- 1 | /** 2 | A Bitcoin contract which can be spent by anyone but only to a specific pubKeyHash (address) 3 | **/ 4 | 5 | import "util.scrypt"; 6 | 7 | contract AnyoneCanSpend { 8 | Ripemd160 pubKeyHash; 9 | 10 | public function unlock(SigHashPreimage txPreimage) { 11 | SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID; 12 | // this ensures the preimage is for the current tx 13 | require(Util.checkPreimageSigHashType(txPreimage, sigHashType)); 14 | 15 | bytes lockingScript = Util.buildPublicKeyHashScript(this.pubKeyHash); 16 | int inputAmount = Util.value(txPreimage); 17 | int outputAmount = inputAmount - 546; // minFee 18 | bytes output = Util.buildOutput(lockingScript, outputAmount); 19 | 20 | Sha256 hashOutputs = hash256(output); 21 | require(hashOutputs == Util.hashOutputs(txPreimage)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/res/schnorr.scrypt: -------------------------------------------------------------------------------- 1 | import "ecVerify.scrypt"; 2 | 3 | // Schnorr signatures verification for secp256k1 4 | contract Schnorr { 5 | public function verify(Sig sig, PubKey pubKey, bytes msg, int lambda, 6 | Point R, PointMulAux rAux, 7 | Point E, PointMulAux eAux, 8 | Point S, PointMulAux sAux) { 9 | 10 | int r = unpack(sig[ : 32]); 11 | int s = unpack(sig[32 : 64]); 12 | 13 | // R = r * G 14 | require(ECVerify.isMul(ECVerify.G, r, R, rAux)); 15 | 16 | // e = Hash(r || P || msg) 17 | int e = unpack(sha256(pack(r) + pubKey + msg)); 18 | 19 | // E = e * P 20 | Point P = ECVerify.pubKey2Point(pubKey); 21 | require(ECVerify.isMul(P, e, E, eAux)); 22 | 23 | // S = s * G 24 | require(ECVerify.isMul(ECVerify.G, s, S, sAux)); 25 | 26 | // S == R + H? 27 | require(ECVerify.isSum(R, E, lambda, S)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/res/counter.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | type State = int; 4 | type Amount = int; 5 | 6 | contract Counter { 7 | public function increment(SigHashPreimage txPreimage, Amount amount) { 8 | require(Tx.checkPreimage(txPreimage)); 9 | 10 | bytes scriptCode = Util.scriptCode(txPreimage); 11 | int scriptLen = len(scriptCode); 12 | 13 | // state (i.e., counter value) is at the end 14 | State counter = unpack(scriptCode[scriptLen - Util.DataLen :]); 15 | // increment counter 16 | bytes scriptCode_ = scriptCode[: scriptLen - Util.DataLen] + num2bin(counter + 1, Util.DataLen); 17 | bytes output = Util.buildOutput(scriptCode_, amount); 18 | // ensure output is expected: amount is same with specified 19 | // also output script is the same with scriptCode except counter incremented 20 | require(hash256(output) == Util.hashOutputs(txPreimage)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/res/testMerkleTree.scrypt: -------------------------------------------------------------------------------- 1 | import "./merkleTree.scrypt"; 2 | 3 | contract MerkleTreeTest { 4 | public function testCalculateMerkleRoot(bytes leaf, bytes merklePath, bytes merkleRoot) { 5 | require(MerkleTree.calculateMerkleRoot(leaf, merklePath) == merkleRoot); 6 | } 7 | 8 | public function testVerifyLeaf(bytes leaf, bytes merklePath, bytes merkleRoot) { 9 | require(MerkleTree.verifyLeaf(leaf, merklePath, merkleRoot)); 10 | } 11 | 12 | public function testUpdateLeaf(bytes oldLeaf, bytes newLeaf, bytes merklePath, bytes oldMerkleRoot, bytes newMerkleRoot) { 13 | require(MerkleTree.updateLeaf(oldLeaf, newLeaf, merklePath, oldMerkleRoot) == newMerkleRoot); 14 | } 15 | 16 | public function testAddLeaf(bytes lastLeaf, bytes lastMerklePath, bytes oldMerkleRoot, bytes newLeaf, bytes newMerkleRoot) { 17 | require(MerkleTree.addLeaf(lastLeaf, lastMerklePath, oldMerkleRoot, newLeaf) == newMerkleRoot); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/res/alias.scrypt: -------------------------------------------------------------------------------- 1 | 2 | 3 | type Male = Person; 4 | type Female = Person; 5 | type Integer = int; 6 | type Name = bytes; 7 | type Age = Integer; 8 | type Token = Integer; 9 | 10 | type Tokens = Token[3]; 11 | 12 | type Coinbase = bytes; 13 | type Time = int; 14 | type Height = int; 15 | type MaleAAA = Male; 16 | struct Person { 17 | Age age; 18 | Name name; 19 | Token token; 20 | } 21 | 22 | struct Block { 23 | Height height; 24 | Time time; 25 | Coinbase coinbase; 26 | } 27 | 28 | contract Alias { 29 | Female alice; 30 | 31 | constructor(Female person) { 32 | this.alice = person; 33 | } 34 | 35 | 36 | 37 | public function unlock(MaleAAA bob) { 38 | Age aliceAge = bob.age; 39 | Age bobAge = this.alice.age; 40 | 41 | require(aliceAge + bobAge > 10); 42 | 43 | } 44 | 45 | public function setToken(Tokens tokens) { 46 | this.alice.token = tokens[0]; 47 | require(this.alice.token == 10); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /test/test_contract_hashpuzzle.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import hashlib 3 | 4 | import scryptlib.utils 5 | import scryptlib.contract 6 | from scryptlib.types import SigHashPreimage, Int, Ripemd160, Sha256, PubKey, Sig, Bytes 7 | 8 | import bitcoinx 9 | from bitcoinx import SigHash, TxOutput, Script, PrivateKey, P2PKH_Address, Bitcoin, pack_byte 10 | 11 | 12 | contract = './test/res/hashpuzzle.scrypt' 13 | 14 | compiler_result = scryptlib.utils.compile_contract(contract) 15 | desc = compiler_result.to_desc() 16 | 17 | secret = b'abc' 18 | h_secret = hashlib.sha256(secret).digest() 19 | 20 | HashPuzzle = scryptlib.contract.build_contract_class(desc) 21 | hash_puzzle = HashPuzzle(Sha256(h_secret)) 22 | 23 | 24 | def test_verify_correct(): 25 | verify_result = hash_puzzle.verify(Bytes(secret)).verify() 26 | assert verify_result == True 27 | 28 | 29 | def test_verify_incorrect(): 30 | verify_result = hash_puzzle.verify(Bytes(secret + b'ff')).verify() 31 | assert verify_result == False 32 | 33 | -------------------------------------------------------------------------------- /test/res/dynamicArrayDemo.scrypt: -------------------------------------------------------------------------------- 1 | import "dynamicArray.scrypt"; 2 | 3 | contract ArrayDemo { 4 | static const int size_test = 7; 5 | 6 | public function test(int _x) { 7 | Array a = new Array(b'', 1); 8 | loop (size_test) : i { 9 | a.push(i); 10 | } 11 | loop (size_test) : i { 12 | require(a.get(i) == i); 13 | } 14 | loop (size_test) : i { 15 | a.set(i, 17 * i); 16 | loop (size_test) : j { 17 | if (i != j) { 18 | require(a.get(j) == j); 19 | } 20 | else { 21 | require(a.get(j) == 17 * j); 22 | } 23 | } 24 | a.set(i, i); 25 | } 26 | loop (size_test) : i { 27 | require(a.get(i) == i); 28 | } 29 | loop (size_test) : i { 30 | require(a.pop() == size_test - 1 - i); 31 | } 32 | require(a.length() == 0); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/res/tokenSale.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * A toy token sale 5 | */ 6 | contract TokenSale { 7 | // satoshis / token 8 | int price; 9 | 10 | constructor(int price) { 11 | this.price = price; 12 | } 13 | 14 | public function buy(PubKey buyer, int numTokens, SigHashPreimage txPreimage) { 15 | // this ensures the preimage is for the current tx 16 | require(Tx.checkPreimage(txPreimage)); 17 | 18 | // read previous locking script 19 | bytes lockingScript = Util.scriptCode(txPreimage); 20 | int scriptLen = len(lockingScript); 21 | int oldBalance = Util.value(txPreimage); 22 | int newBalance = oldBalance + numTokens * this.price; 23 | 24 | // write new locking script 25 | bytes lockingScript_ = lockingScript + buyer + num2bin(numTokens, Util.DataLen); 26 | bytes output = Util.buildOutput(lockingScript_, newBalance); 27 | require(hash256(output) == Util.hashOutputs(txPreimage)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/test_optimized_compilation.py: -------------------------------------------------------------------------------- 1 | import scryptlib.utils 2 | import scryptlib.contract 3 | 4 | 5 | contract = './test/res/demo.scrypt' 6 | 7 | compiler_result_debug = scryptlib.utils.compile_contract(contract, debug=True) 8 | desc_debug = compiler_result_debug.to_desc() 9 | 10 | DemoDebug = scryptlib.contract.build_contract_class(desc_debug) 11 | demo_debug = DemoDebug(7, 4) 12 | 13 | compiler_result_optimized = scryptlib.utils.compile_contract(contract, debug=False) 14 | desc_optimized = compiler_result_optimized.to_desc() 15 | 16 | DemoOptimized = scryptlib.contract.build_contract_class(desc_optimized) 17 | demo_optimized = DemoOptimized(7, 4) 18 | 19 | 20 | def test_smaller_script_size(): 21 | assert len(demo_debug.locking_script) > len(demo_optimized.locking_script) 22 | 23 | 24 | def test_verify_optimized(): 25 | verify_result = demo_optimized.add(7 + 4).verify() 26 | assert verify_result == True 27 | 28 | verify_result = demo_optimized.add(7 - 4).verify() 29 | assert verify_result == False 30 | 31 | -------------------------------------------------------------------------------- /test/test_multi_contract.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | 6 | 7 | contract = ''' 8 | contract Demo1 { 9 | int x; 10 | int y; 11 | 12 | constructor(int x, int y) { 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | public function sub(int z) { 18 | require(z == this.x - this.y); 19 | } 20 | } 21 | 22 | contract Demo2 { 23 | int x; 24 | 25 | public function test(int y) { 26 | require(this.x == y); 27 | 28 | Demo1 demo1 = new Demo1(this.x, y); 29 | require(demo1.sub(this.x - y)); 30 | } 31 | } 32 | ''' 33 | 34 | compiler_result = scryptlib.utils.compile_contract(contract, from_string=True) 35 | desc = compiler_result.to_desc() 36 | 37 | # We should always get the lastly defined contract here. 38 | Demo2 = scryptlib.contract.build_contract_class(desc) 39 | demo = Demo2(7) 40 | 41 | def test_verify_correct(): 42 | verify_result = demo.test(7).verify() 43 | assert verify_result == True 44 | 45 | -------------------------------------------------------------------------------- /test/test_contract_clone.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, Ripemd160, SigHashPreimage 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, TxOutput 9 | 10 | 11 | contract = './test/res/clone.scrypt' 12 | 13 | compiler_result = scryptlib.utils.compile_contract(contract) 14 | desc = compiler_result.to_desc() 15 | 16 | Clone = scryptlib.contract.build_contract_class(desc) 17 | clone = Clone() 18 | 19 | context = scryptlib.utils.create_dummy_input_context() 20 | context.utxo.script_pubkey = clone.locking_script 21 | 22 | new_locking_script = clone.locking_script 23 | tx_out = TxOutput(value=0, script_pubkey=new_locking_script) 24 | context.tx.outputs.append(tx_out) 25 | 26 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 27 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 28 | 29 | 30 | def test_verify_correct(): 31 | verify_result = clone.unlock(SigHashPreimage(preimage)).verify(context) 32 | assert verify_result == True 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/res/sighash.scrypt: -------------------------------------------------------------------------------- 1 | import "ec.scrypt"; 2 | 3 | // a template to implement any new SIGHASH flags 4 | contract UniversalSigHash { 5 | Point pubKey; 6 | 7 | // sig is with SIGHASH flag SIGHASH_NOINPUT 8 | public function checkSigHashNoInput(Signature sig, SigHashPreimage sighash) { 9 | // get sighash preimage using SIGHASH_ALL 10 | require(Tx.checkPreimage(sighash)); 11 | 12 | /* reconstruct the new sighash being signed */ 13 | bytes sighash1 = sighash[: 4]; 14 | // set item 2, 3, and 4 to 0 15 | bytes blankedSighash2to3 = b'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 16 | bytes sighash5to10 = sighash[104 : ]; 17 | bytes sighashNew = sighash1 + blankedSighash2to3 + sighash5to10; 18 | 19 | // check signature against the new sighash using elliptic curve library 20 | require(EC.verifySig(sighashNew, sig, this.pubKey)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 kala-tech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/res/oracle.scrypt: -------------------------------------------------------------------------------- 1 | import "ecVerify.scrypt"; 2 | 3 | // an oracle library signing any data using ECDSA 4 | library Oracle { 5 | 6 | // verify data is signed by the oracle with given public key 7 | static function verifyData(bytes data, Sig sig, PubKey oraclePubKey, PubKey derivedOraclePubKey, PubKey X, 8 | int lambda, SigHashPreimage txPreimage) : bool { 9 | // sha256 data 10 | bytes hash = sha256(data); 11 | 12 | // We interpret hash as little endian, even though it's big endian. 13 | // This does not matter as long as it is converted to a 256-bit integer. We do not change endian to save script size 14 | PrivKey x = PrivKey(Utils.fromLEUnsigned(hash)); 15 | 16 | // verify X = x * G? 17 | require(Tx.checkPreimageAdvanced(txPreimage, x, X, Tx.invK, Tx.r, Tx.rBigEndian, SigHashType(SigHash.NONE | SigHash.FORKID))); 18 | 19 | // verify P' = P + X 20 | require(ECVerify.isPubKeySum(oraclePubKey, X, lambda, derivedOraclePubKey)); 21 | 22 | // verify signature is from oracle, who knows p' = p + x 23 | return checkSig(sig, derivedOraclePubKey); 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/test_contract_optimal_push_tx.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import hashlib 3 | 4 | import scryptlib.utils 5 | import scryptlib.contract 6 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage 7 | 8 | import bitcoinx 9 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script 10 | 11 | 12 | input_sats = 100000 13 | MSB_THRESHOLD = 0x7E 14 | 15 | contract = './test/res/optimalPushtx.scrypt' 16 | 17 | compiler_result = scryptlib.utils.compile_contract(contract) 18 | desc = compiler_result.to_desc() 19 | 20 | OPTX = scryptlib.contract.build_contract_class(desc) 21 | optx = OPTX() 22 | 23 | context = scryptlib.utils.create_dummy_input_context() 24 | context.utxo.script_pubkey = optx.locking_script 25 | context.utxo.value = input_sats 26 | 27 | 28 | def test_verify_correct(): 29 | i = 0 30 | while True: 31 | context.tx.locktime = i 32 | preimage = scryptlib.utils.get_preimage_from_input_context(context) 33 | dh = hashlib.sha256(hashlib.sha256(preimage).digest()).digest() 34 | if dh[0] < MSB_THRESHOLD: 35 | break 36 | i += 1 37 | 38 | verify_result = optx.validate(SigHashPreimage(preimage)).verify(context) 39 | assert verify_result == True 40 | -------------------------------------------------------------------------------- /test/res/dynamicArray.scrypt: -------------------------------------------------------------------------------- 1 | // a dynamic array library with fixed-size elements 2 | library Array { 3 | static const bytes EMPTY = b''; 4 | bytes data; 5 | int DATALEN; 6 | 7 | constructor(bytes data, int DATALEN) { 8 | this.data = data; 9 | this.DATALEN = DATALEN; 10 | } 11 | 12 | function clear() : bool { 13 | this.data = EMPTY; 14 | return true; 15 | } 16 | 17 | function push(int x) : bool { 18 | this.data += num2bin(x, this.DATALEN); 19 | return true; 20 | } 21 | 22 | function length() : int { 23 | return len(this.data) / this.DATALEN; 24 | } 25 | 26 | function pop() : int { 27 | int answer = unpack(this.data[this.length() - this.DATALEN :]); 28 | this.data = this.data[: this.length() - this.DATALEN]; 29 | return answer; 30 | } 31 | 32 | function get(int index) : int { 33 | return unpack(this.data[index * this.DATALEN : (index + 1) * this.DATALEN]); 34 | } 35 | 36 | function set(int index, int elem) : bool { 37 | this.data = this.data[: index * this.DATALEN] + num2bin(elem, this.DATALEN) + this.data[(index + 1) * this.DATALEN :]; 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/test_contract_mod_exp.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script 9 | 10 | 11 | contract = './test/res/modExp.scrypt' 12 | 13 | compiler_result = scryptlib.utils.compile_contract(contract) 14 | desc = compiler_result.to_desc() 15 | 16 | Demo = scryptlib.contract.build_contract_class(desc) 17 | 18 | 19 | def test_verify_correct(): 20 | demo = Demo(497) 21 | verify_result = demo.main(4, 13, 445).verify() 22 | assert verify_result == True 23 | 24 | 25 | def test_verify_correct2(): 26 | demo = Demo(10000000000000000000000000000000000000000) 27 | verify_result = demo.main( 28 | 2988348162058574136915891421498819466320163312926952423791023078876139, 29 | 2351399303373464486466122544523690094744975233415544072992656881240319, 30 | 1527229998585248450016808958343740453059 31 | ).verify() 32 | assert verify_result == True 33 | 34 | 35 | def test_verify_incorrect(): 36 | demo = Demo(498) 37 | verify_result = demo.main(4, 13, 445).verify() 38 | assert verify_result == False 39 | -------------------------------------------------------------------------------- /test/res/vanityAddr.scrypt: -------------------------------------------------------------------------------- 1 | import "ecVerify.scrypt"; 2 | 3 | // outsource a vanity address generation 4 | contract VanityAddr { 5 | // buyer's public key 6 | PubKey pubKey; 7 | // vanity address pattern such as vanity prefix "nChain" 8 | bytes pattern; 9 | 10 | // x is the secret seller finds 11 | // all other parameters are auxiliaries of it 12 | public function offerVanityAddr(PrivKey x, PubKey X, PubKey derivedPubKey, int lambda, SigHashPreimage txPreimage) { 13 | // verify = X = x * G? 14 | require(Tx.checkPreimageAdvanced(txPreimage, x, X, Tx.invK, Tx.r, Tx.rBigEndian, SigHashType(SigHash.ALL | SigHash.FORKID))); 15 | 16 | // verify P' = P + X 17 | require(ECVerify.isPubKeySum(this.pubKey, X, lambda, derivedPubKey)); 18 | 19 | // meet requirement 20 | require(matchPattern(derivedPubKey, this.pattern)); 21 | } 22 | 23 | // check if public key's address matches the given pattern 24 | static function matchPattern(PubKey pubKey, bytes pattern) : bool { 25 | // convert public key to address 26 | bytes addr = ripemd160(sha256(pubKey)); 27 | 28 | // prefix match 29 | int l = len(pattern); 30 | return addr[:l] == pattern; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/res/stateSet.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | contract StateSet { 4 | 5 | @state 6 | bytes _setData; 7 | 8 | public function insert(int key, int keyIndex, SigHashPreimage preimage) { 9 | require(Tx.checkPreimage(preimage)); 10 | HashedSet set = new HashedSet(this._setData); 11 | int size = set.size(); 12 | require(!set.has(key, keyIndex)); 13 | require(set.add(key, keyIndex)); 14 | require(set.size() == size + 1); 15 | require(this.passSet(set.data(), preimage)); 16 | } 17 | 18 | public function delete(int key, int keyIndex, SigHashPreimage preimage) { 19 | require(Tx.checkPreimage(preimage)); 20 | HashedSet set = new HashedSet(this._setData); 21 | require(set.has(key, keyIndex)); 22 | require(set.delete(key, keyIndex)); 23 | require(!set.has(key, keyIndex)); 24 | require(this.passSet(set.data(), preimage)); 25 | } 26 | 27 | function passSet(bytes newData, SigHashPreimage preimage) : bool { 28 | this._setData = newData; 29 | bytes outputScript = this.getStateScript(); 30 | bytes output = Util.buildOutput(outputScript, Util.value(preimage)); 31 | return (hash256(output) == Util.hashOutputs(preimage)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/res/ackermann.scrypt: -------------------------------------------------------------------------------- 1 | contract Ackermann { 2 | int a; // a = 2 3 | int b; // b = 1 4 | 5 | static const int LOOPCOUNT = 14; 6 | 7 | function ackermann(int m, int n) : int { 8 | bytes stk = num2bin(m, 1); 9 | 10 | // run this function off chain to get the loop count and set it here 11 | // e.g., (2, 1) requires 14 loops, (3, 5) 42438 12 | loop (LOOPCOUNT) { 13 | if (len(stk) > 0) { 14 | bytes top = stk[0 : 1]; 15 | m = unpack(top); 16 | 17 | // pop 18 | stk = stk[1 : len(stk)]; 19 | 20 | if (m == 0) { 21 | n = n + m + 1; 22 | } 23 | else if (n == 0) { 24 | n++; 25 | m--; 26 | // push 27 | stk = num2bin(m, 1) + stk; 28 | } 29 | else { 30 | stk = num2bin(m - 1, 1) + stk; 31 | stk = num2bin(m, 1) + stk; 32 | n--; 33 | } 34 | } 35 | } 36 | 37 | return n; 38 | } 39 | 40 | // y = 5 41 | public function unlock(int y) { 42 | require(y == this.ackermann(this.a, this.b)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import codecs 4 | from setuptools import setup 5 | from os import path 6 | 7 | 8 | def read(rel_path): 9 | here = path.abspath(path.dirname(__file__)) 10 | with codecs.open(path.join(here, rel_path), 'r') as fp: 11 | return fp.read() 12 | 13 | 14 | def get_version(rel_path): 15 | for line in read(rel_path).splitlines(): 16 | if line.startswith('__version__'): 17 | delim = '"' if '"' in line else '\'' 18 | return line.split(delim)[1] 19 | else: 20 | raise RuntimeError('Unable to find version string.') 21 | 22 | 23 | here = path.abspath(path.dirname(__file__)) 24 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 25 | long_description = f.read() 26 | 27 | setup(long_description=long_description, 28 | long_description_content_type="text/markdown", 29 | name='scryptlib', 30 | version=get_version('scryptlib/__init__.py'), 31 | description='Python SDK for integration of sCrypt Bitcoin SV smart contracts.', 32 | keywords='scrypt scryptlib bitcoin bsv blockchain', 33 | author='Kala', 34 | url='https://www.github.com/sCrypt-Inc/py-scryptlib', 35 | packages=['scryptlib'], 36 | install_requires=['bitcoinX==0.6.0'], 37 | python_requires='>=3.7', 38 | 39 | # Dependencies to run all tests. 40 | extras_require = { 41 | 'testing': ['pytest', 'rabin', 'ecdsa'] 42 | } 43 | ) 44 | -------------------------------------------------------------------------------- /test/res/token.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * A toy token example between two holders 5 | */ 6 | 7 | struct Account { 8 | PubKey pubKey; 9 | int balance; 10 | } 11 | contract Token { 12 | 13 | @state 14 | Account[2] accounts; 15 | 16 | public function transfer(PubKey sender, Sig senderSig, PubKey receiver, int value /* amount to be transferred */, SigHashPreimage txPreimage, int amount) { 17 | // this ensures the preimage is for the current tx 18 | require(Tx.checkPreimage(txPreimage)); 19 | 20 | // authorize 21 | require(checkSig(senderSig, sender)); 22 | 23 | // only between two holders 24 | require(sender == this.accounts[0].pubKey && receiver == this.accounts[1].pubKey || sender == this.accounts[1].pubKey && receiver == this.accounts[0].pubKey); 25 | 26 | 27 | // transfer 28 | if (sender == this.accounts[0].pubKey) { 29 | require(this.accounts[0].balance >= value); 30 | this.accounts[0].balance -= value; 31 | this.accounts[1].balance += value; 32 | } 33 | else { 34 | require(this.accounts[1].balance >= value); 35 | this.accounts[1].balance -= value; 36 | this.accounts[0].balance += value; 37 | } 38 | 39 | // write new locking script 40 | bytes stateScript = this.getStateScript(); 41 | bytes output = Util.buildOutput(stateScript, amount); 42 | require(hash256(output) == Util.hashOutputs(txPreimage)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/test_contract_p2sh.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import hashlib 3 | 4 | import scryptlib.utils 5 | import scryptlib.contract 6 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage, Ripemd160, Bytes 7 | 8 | import bitcoinx 9 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, ripemd160 10 | 11 | 12 | input_sats = 100000 13 | 14 | contract = './test/res/p2sh.scrypt' 15 | compiler_result = scryptlib.utils.compile_contract(contract) 16 | desc = compiler_result.to_desc() 17 | P2SH = scryptlib.contract.build_contract_class(desc) 18 | 19 | contract = './test/res/counter.scrypt' 20 | compiler_result = scryptlib.utils.compile_contract(contract) 21 | desc = compiler_result.to_desc() 22 | DemoContract = scryptlib.contract.build_contract_class(desc) 23 | 24 | demo_contract = DemoContract() 25 | script_hash_sha256 = hashlib.sha256(demo_contract.code_part.to_bytes()).digest() 26 | script_hash = ripemd160(script_hash_sha256) 27 | 28 | p2sh = P2SH(Ripemd160(script_hash)) 29 | 30 | 31 | def test_verify_correct(): 32 | context = scryptlib.utils.create_dummy_input_context() 33 | context.utxo.script_pubkey = p2sh.locking_script 34 | context.utxo.value = input_sats 35 | 36 | tx_out = TxOutput(value=input_sats, script_pubkey=demo_contract.code_part) 37 | context.tx.outputs.append(tx_out) 38 | 39 | preimage = scryptlib.utils.get_preimage_from_input_context(context) 40 | 41 | verify_result = p2sh.redeem(Bytes(demo_contract.code_part.to_bytes()), 42 | SigHashPreimage(preimage)).verify(context) 43 | assert verify_result == True 44 | 45 | -------------------------------------------------------------------------------- /test/test_contract_p2pkh.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, PubKeyHash 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, PrivateKey, pack_byte 9 | 10 | 11 | key_priv = PrivateKey.from_arbitrary_bytes(b'test123') 12 | key_pub = key_priv.public_key 13 | pubkey_hash = key_pub.hash160() 14 | 15 | wrong_key_priv = PrivateKey.from_arbitrary_bytes(b'somethingelse') 16 | wrong_key_pub = wrong_key_priv.public_key 17 | 18 | contract = './test/res/p2pkh.scrypt' 19 | 20 | compiler_result = scryptlib.utils.compile_contract(contract) 21 | desc = compiler_result.to_desc() 22 | 23 | P2PKH = scryptlib.contract.build_contract_class(desc) 24 | p2pkh_obj = P2PKH(PubKeyHash(pubkey_hash)) 25 | 26 | context = scryptlib.utils.create_dummy_input_context() 27 | 28 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 29 | input_idx = 0 30 | utxo_satoshis = context.utxo.value 31 | sighash = context.tx.signature_hash(input_idx, utxo_satoshis, p2pkh_obj.locking_script, sighash_flag) 32 | 33 | 34 | def test_verify_correct_key(): 35 | sig = key_priv.sign(sighash, hasher=None) 36 | sig = sig + pack_byte(sighash_flag) 37 | verify_result = p2pkh_obj.unlock(Sig(sig), PubKey(key_pub)).verify(context) 38 | assert verify_result == True 39 | 40 | 41 | def test_verify_wrong(): 42 | sig = wrong_key_priv.sign(sighash, hasher=None) 43 | sig = sig + pack_byte(sighash_flag) 44 | with pytest.raises(bitcoinx.VerifyFailed): 45 | p2pkh_obj.unlock(Sig(sig), PubKey(wrong_key_pub)).verify(context) 46 | 47 | -------------------------------------------------------------------------------- /test/res/structdemo.scrypt: -------------------------------------------------------------------------------- 1 | type Integer = int; 2 | type Name = bytes; 3 | type Species = bytes; 4 | type Age = Integer; 5 | type Address = bytes; 6 | 7 | struct Person { 8 | Name name; 9 | Address addr; 10 | bool leftHanded; 11 | Age age; 12 | Pet[2] pets; 13 | } 14 | 15 | struct Pet { 16 | Name name; 17 | Species species; 18 | } 19 | 20 | type Female = Person; 21 | type Male = Person; 22 | 23 | struct Block { 24 | bytes hash; 25 | int time; 26 | bytes coinbase; 27 | } 28 | 29 | contract StructDemo { 30 | Person person; 31 | 32 | constructor(Person male) { 33 | this.person = male; 34 | } 35 | 36 | public function main(Female person1) { 37 | 38 | require(person1.leftHanded == this.person.leftHanded); 39 | require(person1.addr == this.person.addr); 40 | require(person1.age == this.person.age); 41 | 42 | require(person1.pets == this.person.pets); 43 | 44 | this.person = this.incAge(person1); 45 | require(this.person.age == 34); 46 | 47 | Block block = this.genisBlock(person1); 48 | require(block.coinbase == b'7361746f736869206e616b616d6f746f'); 49 | require(block.hash == b'000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'); 50 | } 51 | 52 | function incAge(Female p1) : Person { 53 | p1.age++; 54 | return p1; 55 | } 56 | 57 | function genisBlock(Male p1) : Block { 58 | 59 | Block blk = { b'000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 1231006505, p1.name }; 60 | 61 | return blk; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/test_contract_token_sale.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, sha256 9 | 10 | 11 | n_tokens = 21 12 | token_price_sats = 100 13 | 14 | in_sats = 100000 15 | 16 | key_priv = PrivateKey.from_arbitrary_bytes(b'test123') 17 | key_pub = key_priv.public_key 18 | pubkey_hash = key_pub.hash160() 19 | 20 | contract = './test/res/tokenSale.scrypt' 21 | 22 | compiler_result = scryptlib.utils.compile_contract(contract) 23 | desc = compiler_result.to_desc() 24 | 25 | TokenSale = scryptlib.contract.build_contract_class(desc) 26 | token_sale = TokenSale(token_price_sats) 27 | 28 | # Create context and set prev locking script. 29 | context = scryptlib.utils.create_dummy_input_context() 30 | context.utxo.script_pubkey = token_sale.locking_script 31 | context.utxo.value = in_sats 32 | 33 | def get_preimage_after_purchase(key_pub): 34 | new_locking_script = Script(token_sale.locking_script.to_bytes() + 35 | key_pub.to_bytes() + scryptlib.utils.get_push_int(n_tokens)[1:]) 36 | tx_out = TxOutput(value=in_sats + n_tokens * token_price_sats, script_pubkey=new_locking_script) 37 | context.tx.outputs.append(tx_out) 38 | 39 | return scryptlib.utils.get_preimage_from_input_context(context) 40 | 41 | 42 | def test_verify_correct(): 43 | preimage = get_preimage_after_purchase(key_pub) 44 | 45 | verify_result = token_sale.buy(PubKey(key_pub), n_tokens, SigHashPreimage(preimage)).verify(context) 46 | assert verify_result == True 47 | -------------------------------------------------------------------------------- /test/res/asm.scrypt: -------------------------------------------------------------------------------- 1 | contract Asm { 2 | public function double(int a, int b) { 3 | asm { 4 | OP_DUP 5 | OP_ADD 6 | OP_NUMEQUAL 7 | } 8 | } 9 | 10 | function equalImpl(int a) : bool { 11 | asm { 12 | // mix all 13 | $x 14 | ab12 15 | OP_SIZE 16 | OP_NIP 17 | OP_MUL 18 | OP_1 19 | OP_MUL 20 | $x 21 | OP_SUB 22 | OP_EQUAL 23 | } 24 | } 25 | 26 | public function equal(int a) { 27 | require(this.equalImpl(a)); 28 | } 29 | 30 | public function p2pkh(Sig sig, PubKey pubKey) { 31 | asm { 32 | OP_DUP 33 | OP_HASH160 34 | $pkh 35 | OP_EQUALVERIFY 36 | OP_CHECKSIG 37 | } 38 | } 39 | 40 | function len(bytes b) : int { 41 | asm { 42 | OP_SIZE 43 | OP_NIP 44 | } 45 | } 46 | 47 | function lenFail(bytes b) : int { 48 | asm { 49 | // this is wrong since the last value on stack will be considered as the return value 50 | OP_SIZE 51 | OP_0 52 | } 53 | } 54 | 55 | public function checkLen(bytes b, int l) { 56 | require(this.len(b) == l); 57 | require(this.len(b) == l); 58 | require(this.len(b) == l); 59 | } 60 | 61 | public function checkLenFail(bytes b, int l) { 62 | // expect to fail after multiple calls since the stack is messed 63 | require(this.lenFail(b) == l); 64 | require(this.lenFail(b) == l); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/res/stateStruct.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | import "serializer.scrypt"; 3 | 4 | // state as a struct 5 | struct State { 6 | int counter; 7 | bytes buf; 8 | bool flag; 9 | } 10 | 11 | /* 12 | * test structual state serializing/deserializing 13 | */ 14 | contract StateStruct { 15 | static function deserialize(bytes buf) : State { 16 | Reader r = new Reader(buf); 17 | auto counter = r.readInt(); 18 | auto b = r.readBytes(); 19 | auto flag = r.readBool(); 20 | 21 | return { counter, b, flag }; 22 | } 23 | 24 | static function serialize(State s) : bytes { 25 | bytes sBuf = Writer.writeInt(s.counter) + Writer.writeBytes(s.buf) + Writer.writeBool(s.flag); 26 | return Writer.serializeState(sBuf); 27 | } 28 | 29 | public function mutate(SigHashPreimage txPreimage, int amount) { 30 | require(Tx.checkPreimage(txPreimage)); 31 | bytes scriptCode = Util.scriptCode(txPreimage); 32 | 33 | // read/deserialize state 34 | int stateStart = Reader.getStateStart(scriptCode); 35 | State states = StateStruct.deserialize(scriptCode[stateStart :]); 36 | require(states == { 11, b'1234', true }); 37 | 38 | // mutate state 39 | states.counter++; 40 | states.buf += b'ffff'; 41 | states.flag = !states.flag; 42 | 43 | // write/serialize state 44 | bytes stateBuf = StateStruct.serialize(states); 45 | 46 | bytes scriptCode_ = scriptCode[: stateStart] + stateBuf; 47 | bytes output = Util.buildOutput(scriptCode_, amount); 48 | require(hash256(output) == Util.hashOutputs(txPreimage)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/test_contract_matrix.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int 6 | 7 | import bitcoinx 8 | 9 | 10 | contract = './test/res/matrix.scrypt' 11 | 12 | compiler_result = scryptlib.utils.compile_contract(contract, debug=False) 13 | desc = compiler_result.to_desc() 14 | 15 | Matrix = scryptlib.contract.build_contract_class(desc) 16 | 17 | matrix = Matrix() 18 | 19 | 20 | def test_verify_main_correct(): 21 | verify_result = matrix.main([ 22 | [10, 10, 10, 10], 23 | [20, 20, 20, 20], 24 | [30, 30, 30, 30], 25 | [40, 40, 40, 40] 26 | ]).verify() 27 | assert verify_result == True 28 | 29 | 30 | def test_verify_main_correct_mixed_types(): 31 | verify_result = matrix.main([ 32 | [10, 10, 10, Int(10)], 33 | [20, 20, 20, 20], 34 | [30, Int(30), 30, 30], 35 | [40, Int(40), Int(40), 40] 36 | ]).verify() 37 | assert verify_result == True 38 | 39 | 40 | def test_verify_main_wrong_val(): 41 | verify_result = matrix.main([ 42 | [10, 10, 10, Int(10)], 43 | [20, 20, 20, 10], 44 | [30, Int(30), 30, 30], 45 | [40, Int(40), Int(40), 40] 46 | ]).verify() 47 | assert verify_result == False 48 | 49 | 50 | def test_verify_main_wrong_param_format(): 51 | with pytest.raises(Exception): 52 | verify_result = matrix.main([ 53 | [10, 10, 10, Int(10)], 54 | [20, 20, 20], 55 | [30, Int(30), 30, 30] 56 | ]).verify() 57 | -------------------------------------------------------------------------------- /test/res/advancedCounter.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * Demonstrates TxAdvanced, with external funding (additional input) and a change output 5 | */ 6 | contract AdvancedCounter { 7 | public function increment(SigHashPreimage txPreimage, int amount, Ripemd160 changePKH, int changeSats) { 8 | SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID; 9 | // this ensures the preimage is for the current tx 10 | require(Util.checkPreimageSigHashType(txPreimage, sigHashType)); 11 | 12 | bytes scriptCode = Util.scriptCode(txPreimage); 13 | int scriptLen = len(scriptCode); 14 | 15 | // state (i.e., counter value) is at the end 16 | int counter = unpack(scriptCode[scriptLen - Util.DataLen :]); 17 | 18 | // Expect the counter to be incremented in the new transaction state 19 | bytes scriptCode_ = scriptCode[: scriptLen - Util.DataLen] + num2bin(counter + 1, Util.DataLen); 20 | 21 | bytes counterOutput = Util.buildOutput(scriptCode_, amount); 22 | 23 | // Expect the additional CHANGE output 24 | bytes changeScript = Util.buildPublicKeyHashScript(changePKH); 25 | bytes changeOutput = Util.buildOutput(changeScript, changeSats); 26 | 27 | // output: amount + scriptlen + script 28 | Sha256 hashOutputs = hash256(counterOutput + changeOutput); 29 | 30 | // ensure output matches what we expect: 31 | // - amount is same as specified 32 | // - output script is the same as scriptCode except the counter was incremented 33 | // - expected CHANGE output script is there 34 | require(hashOutputs == Util.hashOutputs(txPreimage)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/res/stateMap.scrypt: -------------------------------------------------------------------------------- 1 | 2 | struct MapEntry { 3 | int key; 4 | int val; 5 | int keyIndex; 6 | } 7 | 8 | contract StateMap { 9 | @state 10 | bytes _mpData; //Save the serialized data of the map 11 | 12 | // Add key-value pairs to the map 13 | public function insert(MapEntry entry, SigHashPreimage preimage) { 14 | require(Tx.checkPreimage(preimage)); 15 | HashedMap map = new HashedMap(this._mpData); 16 | require(map.set({entry.key, entry.keyIndex}, entry.val)); 17 | require(this.passMap(map.data(), preimage)); 18 | } 19 | 20 | // update key-value pairs in the map 21 | public function update(MapEntry entry, SigHashPreimage preimage) { 22 | require(Tx.checkPreimage(preimage)); 23 | HashedMap map = new HashedMap(this._mpData); 24 | require(map.set({entry.key, entry.keyIndex}, entry.val)); 25 | require(this.passMap(map.data(), preimage)); 26 | } 27 | 28 | // delete key-value pairs in the map 29 | public function delete(int key, int keyIndex, SigHashPreimage preimage) { 30 | require(Tx.checkPreimage(preimage)); 31 | HashedMap map = new HashedMap(this._mpData); 32 | require(map.delete({key, keyIndex})); 33 | // Serialize map, update state 34 | require(this.passMap(map.data(), preimage)); 35 | } 36 | 37 | // update state _mpData, and build a output contains new state 38 | function passMap(bytes newData, SigHashPreimage preimage) : bool { 39 | this._mpData = newData; 40 | bytes outputScript = this.getStateScript(); 41 | bytes output = Utils.buildOutput(outputScript, SigHash.value(preimage)); 42 | return (hash256(output) == SigHash.hashOutputs(preimage)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/res/matrix.scrypt: -------------------------------------------------------------------------------- 1 | contract Matrix { 2 | static const int N = 4; 3 | static const int[N][N] Identify = [[1, 0, 0, 0], 4 | [0, 1, 0, 0], 5 | [0, 0, 1, 0], 6 | [0, 0, 0, 1]]; 7 | 8 | static function multiply(int[N][N] mat0, int[N][N] mat1) : int[N][N] { 9 | int[N][N] mat2 = [[0, 0, 0, 0], 10 | [0, 0, 0, 0], 11 | [0, 0, 0, 0], 12 | [0, 0, 0, 0]]; 13 | 14 | loop (N) : i { 15 | loop (N) : j { 16 | loop (N) : k { 17 | mat2[i][j] += mat0[i][k] * mat1[k][j]; 18 | } 19 | } 20 | } 21 | 22 | return mat2; 23 | } 24 | 25 | public function main(int[N][N] result) { 26 | int[N][N] mat0 = [[1, 1, 1, 1], 27 | [2, 2, 2, 2], 28 | [3, 3, 3, 3], 29 | [4, 4, 4, 4]]; 30 | 31 | int[N][N] mat1 = [[1, 1, 1, 1], 32 | [2, 2, 2, 2], 33 | [3, 3, 3, 3], 34 | [4, 4, 4, 4]]; 35 | 36 | // A * I = A 37 | auto mat0_ = Matrix.multiply(mat0, Identify); 38 | require(mat0_ == mat0); 39 | 40 | // I * A = A 41 | mat0_ = Matrix.multiply(Identify, mat0); 42 | require(mat0_ == mat0); 43 | 44 | auto product = Matrix.multiply(mat0, mat1); 45 | require(product == [[10, 10, 10, 10], 46 | [20, 20, 20, 20], 47 | [30, 30, 30, 30], 48 | [40, 40, 40, 40]]); 49 | require(product == result); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/test_contract_state_struct.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | import scryptlib.serializer 6 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage 7 | 8 | import bitcoinx 9 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, sha256 10 | 11 | 12 | contract = './test/res/stateStruct.scrypt' 13 | 14 | compiler_result = scryptlib.utils.compile_contract(contract) 15 | desc = compiler_result.to_desc() 16 | 17 | Counter = scryptlib.contract.build_contract_class(desc) 18 | counter = Counter() 19 | 20 | out_sats = 222222 21 | 22 | 23 | def test_verify_correct(): 24 | 25 | # Intial state 26 | state = { 27 | 'counter': 11, 28 | 'buf': b'\x12\x34', 29 | 'flag': True 30 | } 31 | 32 | counter.set_data_part(state) 33 | 34 | # Alter state 35 | state['counter'] += 1 36 | state['buf'] += b'\xff\xff' 37 | state['flag'] = False 38 | 39 | serialized_state = scryptlib.serializer.serialize_state(state) 40 | new_locking_script = Script(counter.code_part.to_bytes() + serialized_state) 41 | 42 | # Deserialize state from new locking script 43 | new_state = scryptlib.serializer.deserialize_state(new_locking_script, state) 44 | assert new_state['counter'] == 12 45 | assert new_state['buf'] == b'\x12\x34\xff\xff' 46 | assert new_state['flag'] == False 47 | 48 | 49 | context = scryptlib.utils.create_dummy_input_context() 50 | context.utxo.script_pubkey = counter.locking_script 51 | 52 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 53 | context.tx.outputs.append(tx_out) 54 | 55 | preimage = scryptlib.utils.get_preimage_from_input_context(context) 56 | 57 | verfiy_result = counter.mutate(SigHashPreimage(preimage), out_sats).verify(context) 58 | assert verfiy_result == True 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /test/res/tokenManualState.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * A toy token example between two holders 5 | */ 6 | contract Token { 7 | public function transfer(PubKey sender, Sig senderSig, PubKey receiver, 8 | int value /* amount to be transferred */, SigHashPreimage txPreimage, int amount) { 9 | 10 | // this ensures the preimage is for the current tx 11 | require(Tx.checkPreimage(txPreimage)); 12 | 13 | // authorize 14 | require(checkSig(senderSig, sender)); 15 | 16 | // read previous locking script 17 | bytes lockingScript = Util.scriptCode(txPreimage); 18 | int scriptLen = len(lockingScript); 19 | 20 | int pkStart = scriptLen - (Util.PubKeyLen + Util.DataLen) * 2; 21 | PubKey pk0 = PubKey(lockingScript[pkStart : pkStart + Util.PubKeyLen]); 22 | int balance0 = unpack(lockingScript[pkStart + Util.PubKeyLen : pkStart + Util.PubKeyLen + Util.DataLen]); 23 | PubKey pk1 = PubKey(lockingScript[pkStart + Util.PubKeyLen + Util.DataLen : pkStart + 2 * Util.PubKeyLen + Util.DataLen]); 24 | int balance1 = unpack(lockingScript[scriptLen - Util.DataLen : ]); 25 | 26 | // only between two holders 27 | require(sender == pk0 && receiver == pk1 || sender == pk1 && receiver == pk0); 28 | 29 | // transfer 30 | if (sender == pk0) { 31 | require(balance0 >= value); 32 | balance0 = balance0 - value; 33 | balance1 = balance1 + value; 34 | } else { 35 | require(balance1 >= value); 36 | balance1 = balance1 - value; 37 | balance0 = balance0 + value; 38 | } 39 | 40 | // write new locking script 41 | bytes lockingScript_ = lockingScript[: pkStart] + pk0 + num2bin(balance0, Util.DataLen) + pk1 + num2bin(balance1, Util.DataLen); 42 | bytes output = Util.buildOutput(lockingScript_, amount); 43 | require(hash256(output) == Util.hashOutputs(txPreimage)); 44 | } 45 | } -------------------------------------------------------------------------------- /test/res/ecdsa.scrypt: -------------------------------------------------------------------------------- 1 | import "ecVerify.scrypt"; 2 | 3 | struct RSPair { 4 | int r; 5 | int s; 6 | } 7 | 8 | // ECDSA signatures verification for secp256k1, for arbitrary message @msg 9 | contract ECDSA { 10 | public function verify(Sig sig, PubKey pubKey, bytes msg, 11 | int invS, Point P, int lambda, Point U1, PointMulAux u1Aux, Point U2, PointMulAux u2Aux) { 12 | 13 | // extract (r, s) from sig 14 | RSPair rs = parseDERSig(sig); 15 | int r = rs.r; 16 | int s = rs.s; 17 | // within range 18 | require(r >= 1 && r < ECVerify.n); 19 | require(s >= 1 && s < ECVerify.n); 20 | 21 | // verify invS 22 | require((s * invS) % ECVerify.n == 1); 23 | 24 | int e = unpack(sha256(msg)); 25 | int u1 = (e * invS) % ECVerify.n; 26 | int u2 = (r * invS) % ECVerify.n; 27 | 28 | // U1 = u1 * G 29 | require(ECVerify.isMul(ECVerify.G, u1, U1, u1Aux)); 30 | 31 | Point Q = ECVerify.pubKey2Point(pubKey); 32 | // U2 = u2 * Q 33 | require(ECVerify.isMul(Q, u2, U2, u2Aux)); 34 | 35 | // P == U1 + U2 36 | require(ECVerify.isSum(U1, U2, lambda, P)); 37 | // cannot be identify 38 | require(P != ECVerify.ZERO); 39 | 40 | require((P.x - r) % ECVerify.n == 0); 41 | } 42 | 43 | // parse signature in DER format to get (r, s) pair 44 | static function parseDERSig(Sig sig) : RSPair { 45 | int rLen = unpack(sig[3 : 4]); 46 | int r = fromBESigned(sig[4 : 4 + rLen]); 47 | 48 | int sLen = unpack(sig[6 + rLen : 7 + rLen]); 49 | int s = fromBESigned(sig[7 + rLen : 7 + rLen + sLen]); 50 | 51 | return { r , s }; 52 | } 53 | 54 | // r & s are signed big endian 55 | static function fromBESigned(bytes b) : int { 56 | // convert big-endian to little-endian: either 32 or 33 bytes 57 | bytes bLE = len(b) == 32 ? reverseBytes(b, 32) : reverseBytes(b, 33); 58 | return unpack(bLE); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/test_contract_rabin.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import rabin 3 | 4 | import scryptlib.utils 5 | import scryptlib.contract 6 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage, Bytes 7 | 8 | import bitcoinx 9 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script 10 | 11 | 12 | contract = './test/res/rabin.scrypt' 13 | 14 | compiler_result = scryptlib.utils.compile_contract(contract) 15 | desc = compiler_result.to_desc() 16 | 17 | RabinSig = scryptlib.contract.build_contract_class(desc) 18 | rabin_obj = RabinSig() 19 | 20 | 21 | def test_verify_correct(): 22 | seed = b'\xff' 23 | p, q = rabin.gen_prime_pair(seed) 24 | n = p * q 25 | message = bytes.fromhex('00112233445566778899aabbccddeeff') 26 | sig, pad = rabin.sign_rabin(p, q, message) 27 | 28 | padding_bytes = b'\x00' * pad 29 | 30 | verify_result = rabin_obj.verifySig( 31 | sig, 32 | Bytes(message), 33 | Bytes(padding_bytes), 34 | n).verify() 35 | assert verify_result == True 36 | 37 | 38 | def test_verify_wrong_padding(): 39 | seed = b'\xff' 40 | p, q = rabin.gen_prime_pair(seed) 41 | n = p * q 42 | message = bytes.fromhex('00112233445566778899aabbccddeeff') 43 | sig, pad = rabin.sign_rabin(p, q, message) 44 | 45 | padding_bytes = b'\x00' * (pad + 1) 46 | 47 | verify_result = rabin_obj.verifySig( 48 | sig, 49 | Bytes(message), 50 | Bytes(padding_bytes), 51 | n).verify() 52 | assert verify_result == False 53 | 54 | 55 | def test_verify_wrong_sig(): 56 | seed = b'\xff' 57 | p, q = rabin.gen_prime_pair(seed) 58 | n = p * q 59 | message = bytes.fromhex('00112233445566778899aabbccddeeff') 60 | sig, pad = rabin.sign_rabin(p, q, message) 61 | 62 | padding_bytes = b'\x00' * (pad + 1) 63 | 64 | verify_result = rabin_obj.verifySig( 65 | sig + 1, 66 | Bytes(message), 67 | Bytes(padding_bytes), 68 | n).verify() 69 | assert verify_result == False 70 | 71 | -------------------------------------------------------------------------------- /test/test_contract_alias.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Bytes 6 | 7 | 8 | contract = './test/res/alias.scrypt' 9 | 10 | compiler_result = scryptlib.utils.compile_contract(contract) 11 | desc = compiler_result.to_desc() 12 | 13 | Alias = scryptlib.contract.build_contract_class(desc) 14 | 15 | type_classes = scryptlib.contract.build_type_classes(desc) 16 | Male = type_classes['Male'] 17 | Female = type_classes['Female'] 18 | Integer = type_classes['Integer'] 19 | Name = type_classes['Name'] 20 | Token = type_classes['Token'] 21 | Tokens = type_classes['Tokens'] 22 | Coinbase = type_classes['Coinbase'] 23 | Time = type_classes['Time'] 24 | Height = type_classes['Height'] 25 | MaleAAA = type_classes['MaleAAA'] 26 | Person = type_classes['Person'] 27 | Block = type_classes['Block'] 28 | 29 | 30 | bob = MaleAAA({ 31 | 'age': 8, 32 | 'name': Bytes(str.encode('Bob', encoding='utf-8')), 33 | 'token': 80 34 | }) 35 | alice = Female({ 36 | 'age': 7, 37 | 'name': Bytes(str.encode('Alice', encoding='utf-8')), 38 | 'token': 150 39 | }) 40 | 41 | alias = Alias(alice) 42 | 43 | 44 | def test_array_unlock_correct(): 45 | verify_result = alias.unlock(bob).verify() 46 | assert verify_result == True 47 | 48 | 49 | def test_unlock_wrong(): 50 | charlie = MaleAAA({ 51 | 'age': 2, 52 | 'name': Bytes(str.encode('Charlie', encoding='utf-8')), 53 | 'token': 10 54 | }) 55 | verify_result = alias.unlock(charlie).verify() 56 | assert verify_result == False 57 | 58 | 59 | def test_set_token_correct(): 60 | verify_result = alias.setToken([10, 20, 25]).verify() 61 | assert verify_result == True 62 | 63 | def test_set_token_incorrect_val(): 64 | verify_result = alias.setToken([11, 20, 25]).verify() 65 | assert verify_result == False 66 | 67 | def test_set_token_wrong_array_len(): 68 | with pytest.raises(Exception): 69 | alias.setToken([11, 20, 25, 23]).verify() 70 | -------------------------------------------------------------------------------- /test/test_contract_asm.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, Bytes, Int, Ripemd160 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, PrivateKey, pack_byte 9 | 10 | 11 | key_priv = PrivateKey.from_arbitrary_bytes(b'test123') 12 | key_pub = key_priv.public_key 13 | pubkey_hash = key_pub.hash160() 14 | 15 | contract = './test/res/asm.scrypt' 16 | 17 | compiler_result = scryptlib.utils.compile_contract(contract) 18 | desc = compiler_result.to_desc() 19 | 20 | Asm = scryptlib.contract.build_contract_class(desc) 21 | asm_vars = { 22 | 'Asm.p2pkh.pkh': Ripemd160(pubkey_hash), 23 | 'Asm.equalImpl.x': Int(11) 24 | } 25 | asm = Asm(asm_vars=asm_vars) 26 | 27 | 28 | def test_verify_double_correct(): 29 | verify_result = asm.double(222, 111).verify() 30 | assert verify_result == True 31 | 32 | 33 | def test_verify_double_wrong(): 34 | verify_result = asm.double(222, 121).verify() 35 | assert verify_result == False 36 | 37 | 38 | def test_verify_equal_correct(): 39 | verify_result = asm.equal(11).verify() 40 | assert verify_result == True 41 | 42 | 43 | def test_verify_equal_wrong(): 44 | verify_result = asm.equal(10).verify() 45 | assert verify_result == False 46 | 47 | 48 | def test_verify_p2pkh_correct(): 49 | context = scryptlib.utils.create_dummy_input_context() 50 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 51 | sighash = context.tx.signature_hash(0, context.utxo.value, asm.locking_script, sighash_flag) 52 | sig = key_priv.sign(sighash, hasher=None) 53 | sig = sig + pack_byte(sighash_flag) 54 | verify_result = asm.p2pkh(Sig(sig), PubKey(key_pub)).verify(context) 55 | assert verify_result == True 56 | 57 | 58 | def test_verify_checklen_correct(): 59 | verify_result = asm.checkLen(Bytes('1122ffee'), 4).verify() 60 | assert verify_result == True 61 | 62 | 63 | def test_verify_checklenfail_correct(): 64 | with pytest.raises(bitcoinx.VerifyFailed): 65 | asm.checkLenFail(Bytes('1122ffee'), 4).verify() 66 | -------------------------------------------------------------------------------- /examples/broadcast_contract_p2pkh.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, Ripemd160 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, PrivateKey, Tx, TxInput, TxOutput, Script, pack_byte, JSONFlags, Bitcoin, hex_str_to_hash 9 | 10 | 11 | unlock_key_priv = PrivateKey.from_WIF('****************************************************') 12 | unlock_key_pub = unlock_key_priv.public_key 13 | addr_dest = '1Pc1iF4g8iVmnu1puvGasSyDcwv2FS1VcH' 14 | prev_txid = 'c1543650beafbf646e75aeeae9b091e4c477362db4a18e740d3f9d2ae250c013' 15 | miner_fee = 120 16 | contract = '../test/res/p2pkh.scrypt' 17 | 18 | compiler_result = scryptlib.utils.compile_contract(contract) 19 | desc = compiler_result.to_desc() 20 | 21 | P2PKH = scryptlib.contract.build_contract_class(desc) 22 | p2pkh_obj = P2PKH(Ripemd160(addr_dest)) 23 | 24 | prev_tx_hash = hex_str_to_hash(prev_txid) 25 | prev_out_idx = 0 26 | 27 | r = requests.get('https://api.whatsonchain.com/v1/bsv/main/tx/{}'.format(prev_txid)).json() 28 | prev_locking_script = Script.from_hex(r['vout'][prev_out_idx]['scriptPubKey']['hex']) 29 | unlocked_satoshis = int(r['vout'][prev_out_idx]['value'] * 10**8) 30 | out_satoshis = unlocked_satoshis - miner_fee 31 | n_sequence = 0xffffffff 32 | 33 | tx_input = TxInput(prev_tx_hash, prev_out_idx, None, n_sequence) 34 | tx_output = TxOutput(out_satoshis, p2pkh_obj.locking_script) 35 | 36 | tx = Tx(2, [tx_input], [tx_output], 0x00000000) 37 | 38 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 39 | sighash = tx.signature_hash(0, unlocked_satoshis, prev_locking_script, sighash_flag) 40 | sig = unlock_key_priv.sign(sighash, hasher=None) 41 | sig = sig + pack_byte(sighash_flag) 42 | 43 | unlock_script = Script() << sig << unlock_key_pub.to_bytes() 44 | tx.inputs[0].script_sig = unlock_script 45 | 46 | 47 | ######## Broadcast transaction ######## 48 | import json 49 | headers = {'Content-Type': 'application/json'} 50 | json_payload = {'txhex': tx.to_hex()} 51 | r = requests.post('https://api.whatsonchain.com/v1/bsv/main/tx/raw', 52 | data=json.dumps(json_payload), 53 | headers=headers, 54 | timeout=30) 55 | print('API response:', r.json()) 56 | -------------------------------------------------------------------------------- /test/test_contract_state_counter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import SigHashPreimage, Int, Bool 6 | 7 | from bitcoinx import SigHash, TxOutput 8 | 9 | 10 | COUNTER_INITIAL_VAL = 0 11 | 12 | contract = './test/res/statecounter.scrypt' 13 | 14 | compiler_result = scryptlib.utils.compile_contract(contract) 15 | desc = compiler_result.to_desc() 16 | 17 | StateCounter = scryptlib.contract.build_contract_class(desc) 18 | counter_obj = StateCounter(COUNTER_INITIAL_VAL) 19 | 20 | 21 | def test_verify_correct_constructor(): 22 | context = scryptlib.utils.create_dummy_input_context() 23 | context.utxo.script_pubkey = counter_obj.locking_script 24 | 25 | new_locking_script = counter_obj.get_state_script({ 26 | "counter": COUNTER_INITIAL_VAL + 1 27 | }) 28 | 29 | tx_out = TxOutput(value=0, script_pubkey=new_locking_script) 30 | context.tx.outputs.append(tx_out) 31 | 32 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 33 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 34 | new_output_satoshis = 0 35 | verify_result = counter_obj.unlock(SigHashPreimage(preimage), Int(new_output_satoshis)).verify(context) 36 | assert verify_result == True 37 | 38 | 39 | def test_verify_correct_member_var(): 40 | counter_obj.counter = 1 41 | 42 | context = scryptlib.utils.create_dummy_input_context() 43 | context.utxo.script_pubkey = counter_obj.locking_script 44 | 45 | new_locking_script = counter_obj.get_state_script({ 46 | "counter": counter_obj.counter + 1 47 | }) 48 | 49 | tx_out = TxOutput(value=0, script_pubkey=new_locking_script) 50 | context.tx.outputs.append(tx_out) 51 | 52 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 53 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 54 | new_output_satoshis = 0 55 | verify_result = counter_obj.unlock(SigHashPreimage(preimage), Int(new_output_satoshis)).verify(context) 56 | assert verify_result == True 57 | 58 | 59 | def test_verify_wrong_type(): 60 | counter_obj.counter = Bool(False) 61 | with pytest.raises(Exception): 62 | counter_obj.locking_script 63 | -------------------------------------------------------------------------------- /test/test_contract_rpuzzle.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage, Ripemd160 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, sha256, PublicKey, int_to_be_bytes, hash160, TxInputContext, Tx, TxInput 9 | 10 | import ecdsa 11 | from hashlib import sha256 as sha256_hashlib 12 | 13 | 14 | 15 | secret = b'This is a secret message!' 16 | h_secret = sha256(secret) 17 | k = h_secret 18 | 19 | secret_wrong = b'This is the wrong secret message!' 20 | h_secret_wrong = sha256(secret_wrong) 21 | k_wrong = h_secret 22 | 23 | G = PublicKey.from_hex('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') 24 | Q = G.multiply(k) 25 | r, _ = Q.to_point() 26 | r = int_to_be_bytes(r) 27 | if r[0] & 0x80: 28 | r0 = pack_byte(0) + r 29 | else: 30 | r0 = r 31 | r_hash = hash160(r0) 32 | 33 | # Ephermal key to generate the r signature 34 | key_priv_R = PrivateKey.from_arbitrary_bytes(b'123test') 35 | key_pub_R = key_priv_R.public_key 36 | 37 | contract = './test/res/rpuzzle.scrypt' 38 | 39 | compiler_result = scryptlib.utils.compile_contract(contract) 40 | desc = compiler_result.to_desc() 41 | 42 | RPuzzle = scryptlib.contract.build_contract_class(desc) 43 | r_puzzle = RPuzzle(Ripemd160(r_hash)) 44 | 45 | utxo_satoshis = 100004 46 | context = scryptlib.utils.create_dummy_input_context() 47 | context.utxo.script_pubkey = r_puzzle.locking_script 48 | context.utxo.value = utxo_satoshis 49 | 50 | 51 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 52 | input_idx = 0 53 | sighash = context.tx.signature_hash(input_idx, utxo_satoshis, r_puzzle.locking_script, sighash_flag) 54 | 55 | class MockHashFunc: 56 | def __init__(self, data): 57 | self.data = data 58 | def digest(self, ): 59 | return self.data 60 | 61 | sk = ecdsa.SigningKey.from_secret_exponent(key_priv_R.to_int(), curve=ecdsa.SECP256k1, hashfunc=MockHashFunc) 62 | sig_r = sk.sign(sighash, k=int.from_bytes(k, 'big'), sigencode=ecdsa.util.sigencode_der) 63 | sig_r = sig_r + pack_byte(sighash_flag) 64 | 65 | sig = key_priv_R.sign(sighash, hasher=None) 66 | sig = sig + pack_byte(sighash_flag) 67 | 68 | 69 | def test_verify_correct(): 70 | verify_result = r_puzzle.unlock(Sig(sig), PubKey(key_pub_R), Sig(sig_r)).verify(context) 71 | assert verify_result == True 72 | 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | out/ 24 | tmp/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # sCrypt 134 | compiler/ 135 | compiler_dist/ 136 | 137 | # PyCharm 138 | .idea/ 139 | -------------------------------------------------------------------------------- /test/test_contract_multisig.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, Ripemd160 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, PrivateKey, pack_byte 9 | 10 | 11 | key_priv_0 = PrivateKey.from_arbitrary_bytes(b'test123') 12 | key_pub_0 = key_priv_0.public_key 13 | pkh_0 = key_pub_0.hash160() 14 | 15 | key_priv_1 = PrivateKey.from_arbitrary_bytes(b'123test') 16 | key_pub_1 = key_priv_1.public_key 17 | pkh_1 = key_pub_1.hash160() 18 | 19 | key_priv_2 = PrivateKey.from_arbitrary_bytes(b'te123st') 20 | key_pub_2 = key_priv_2.public_key 21 | pkh_2 = key_pub_2.hash160() 22 | 23 | contract = './test/res/multiSig.scrypt' 24 | 25 | compiler_result = scryptlib.utils.compile_contract(contract) 26 | desc = compiler_result.to_desc() 27 | 28 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 29 | input_idx = 0 30 | 31 | def test_verify_correct(): 32 | MultiSig = scryptlib.contract.build_contract_class(desc) 33 | multisig = MultiSig([Ripemd160(pkh_0), Ripemd160(pkh_1), Ripemd160(pkh_2)]) 34 | 35 | context = scryptlib.utils.create_dummy_input_context() 36 | sighash = context.tx.signature_hash(input_idx, context.utxo.value, multisig.locking_script, sighash_flag) 37 | 38 | sig_0 = key_priv_0.sign(sighash, hasher=None) + pack_byte(sighash_flag) 39 | sig_1 = key_priv_1.sign(sighash, hasher=None) + pack_byte(sighash_flag) 40 | sig_2 = key_priv_2.sign(sighash, hasher=None) + pack_byte(sighash_flag) 41 | 42 | verify_result = multisig.unlock( 43 | [PubKey(key_pub_0), PubKey(key_pub_1), PubKey(key_pub_2)], 44 | [Sig(sig_0), Sig(sig_1), Sig(sig_2)] 45 | ).verify(context) 46 | assert verify_result == True 47 | 48 | 49 | def test_verify_wrong_key(): 50 | MultiSig = scryptlib.contract.build_contract_class(desc) 51 | multisig = MultiSig([Ripemd160(pkh_0), Ripemd160(pkh_1), Ripemd160(pkh_2)]) 52 | 53 | context = scryptlib.utils.create_dummy_input_context() 54 | sighash = context.tx.signature_hash(input_idx, context.utxo.value, multisig.locking_script, sighash_flag) 55 | 56 | sig_0 = key_priv_0.sign(sighash, hasher=None) + pack_byte(sighash_flag) 57 | sig_1 = key_priv_1.sign(sighash, hasher=None) + pack_byte(sighash_flag) 58 | sig_2 = key_priv_1.sign(sighash, hasher=None) + pack_byte(sighash_flag) 59 | 60 | with pytest.raises(bitcoinx.NullFailError): 61 | verify_result = multisig.unlock( 62 | [PubKey(key_pub_0), PubKey(key_pub_2), PubKey(key_pub_2)], 63 | [Sig(sig_0), Sig(sig_1), Sig(sig_2)] 64 | ).verify(context) 65 | -------------------------------------------------------------------------------- /test/res/advancedTokenSale.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * AdvancedTokenSale 5 | * 6 | * Demonstrates atomic swapping of tokens for satoshis: sales made to a buyer's (public key) 7 | * Uses TxAdvanced, with external funding (additional input) and a change output 8 | * 9 | * Use with: getFundedtxPreimage() and unlockFundedScriptTx() 10 | */ 11 | contract AdvancedTokenSale { 12 | // satoshis / token 13 | int price; 14 | 15 | constructor(int price) { 16 | this.price = price; 17 | } 18 | 19 | public function buy(SigHashPreimage txPreimage, Ripemd160 changePKH, int changeSats, bytes buyer, int numTokens) { 20 | SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID; 21 | // this ensures the preimage is for the current tx 22 | require(Util.checkPreimageSigHashType(txPreimage, sigHashType)); 23 | 24 | // we're using only one byte for the number of tokens purchased 25 | require(0 < numTokens && numTokens < 128); 26 | 27 | bytes scriptCode = Util.scriptCode(txPreimage); 28 | int scriptLen = len(scriptCode); 29 | 30 | int oldBalance = Util.value(txPreimage); 31 | int newBalance = oldBalance + numTokens * this.price; 32 | 33 | // data after the OP_RETURN is a growing list of sales entries: 34 | // PubKeyA,numTokensPurchased 35 | // PubKeyB,numTokensPurchased 36 | bytes newSalesEntry = buyer + num2bin(numTokens, Util.DataLen); 37 | 38 | // expect the latest sales entry to be appended to the previous script/state 39 | 40 | int newStateLen = len(newSalesEntry); 41 | bytes scriptCode_ = scriptCode + num2bin(newStateLen, Util.DataLen) + newSalesEntry; 42 | 43 | // output: amount + scriptlen + script 44 | bytes counterOutput = Util.buildOutput(scriptCode_, newBalance); 45 | 46 | // Expect the additional CHANGE output 47 | bytes changeScript = Util.buildPublicKeyHashScript(changePKH); 48 | // output: amount + scriptlen + script 49 | bytes changeOutput = Util.buildOutput(changeScript, changeSats); 50 | 51 | // expect exactly two outputs 52 | Sha256 hashOutputs = hash256(counterOutput + changeOutput); 53 | 54 | // ensure output matches what we expect: 55 | // - amount/balance reflects funds received from sale 56 | // - output script is the same as scriptCode, with additional sales entry 57 | // - expected change output script is there 58 | require(hashOutputs == Util.hashOutputs(txPreimage)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/test_contract_conwaygol.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import SigHashPreimage, Int 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, TxOutput, Script 9 | 10 | 11 | out_amount = 222222 12 | 13 | contract = './test/res/conwaygol.scrypt' 14 | 15 | compiler_result = scryptlib.utils.compile_contract(contract) 16 | desc = compiler_result.to_desc() 17 | 18 | GameOfLife = scryptlib.contract.build_contract_class(desc) 19 | gol = GameOfLife() 20 | 21 | # Intial GOL board. 22 | b0 = [] 23 | b0.append('00000000000000') 24 | b0.append('00000000000000') 25 | b0.append('00010101000000') 26 | b0.append('00000001000000') 27 | b0.append('00010101000000') 28 | b0.append('00000000000000') 29 | b0.append('00000000000000') 30 | b0 = bytes.fromhex(''.join(b0)) 31 | 32 | gol.set_data_part(b0) 33 | 34 | # Correct next GOL board. 35 | b1 = [] 36 | b1.append('00000000000000') 37 | b1.append('00000100000000') 38 | b1.append('00000101000000') 39 | b1.append('00000000010000') 40 | b1.append('00000101000000') 41 | b1.append('00000100000000') 42 | b1.append('00000000000000') 43 | b1 = bytes.fromhex(''.join(b1)) 44 | 45 | # Wrong next GOL board. 46 | wb1 = [] 47 | wb1.append('00000000001000') 48 | wb1.append('00000100000000') 49 | wb1.append('00000101000000') 50 | wb1.append('00000000000000') 51 | wb1.append('00000101000000') 52 | wb1.append('00000100000000') 53 | wb1.append('00000000010000') 54 | wb1 = bytes.fromhex(''.join(wb1)) 55 | 56 | 57 | def test_verify_correct(): 58 | context = scryptlib.utils.create_dummy_input_context() 59 | context.utxo.script_pubkey = gol.locking_script 60 | 61 | new_locking_script = Script(gol.code_part.to_bytes() + b1) 62 | tx_out = TxOutput(value=out_amount, script_pubkey=new_locking_script) 63 | context.tx.outputs.append(tx_out) 64 | 65 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 66 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 67 | 68 | verify_result = gol.play(out_amount, SigHashPreimage(preimage)).verify(context) 69 | assert verify_result == True 70 | 71 | 72 | def test_verify_wrong_output(): 73 | context = scryptlib.utils.create_dummy_input_context() 74 | context.utxo.script_pubkey = gol.locking_script 75 | 76 | new_locking_script = Script(gol.code_part.to_bytes() + wb1) 77 | tx_out = TxOutput(value=out_amount, script_pubkey=new_locking_script) 78 | context.tx.outputs.append(tx_out) 79 | 80 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 81 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 82 | 83 | verify_result = gol.play(out_amount, SigHashPreimage(preimage)).verify(context) 84 | assert verify_result == False 85 | -------------------------------------------------------------------------------- /test/test_contract_p2generic_ecdsa_pubkey.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import gzip 4 | import json 5 | 6 | from bitcoinx import PublicKey, PrivateKey, sha256, SigHash, NullFailError 7 | from scryptlib import ( 8 | compile_contract, build_contract_class, build_type_classes, 9 | create_dummy_input_context, get_preimage_from_input_context, 10 | SigHashPreimage 11 | ) 12 | 13 | 14 | p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F 15 | 16 | 17 | key_priv = PrivateKey.from_arbitrary_bytes(b'test123') 18 | #key_priv = PrivateKey.from_random() 19 | key_pub = key_priv.public_key 20 | 21 | contract = './test/res/P2GenericECDSAPubKey.scrypt' 22 | 23 | # Compile the contract. Takes a long time! 24 | #compiler_result = compile_contract(contract, debug=False) 25 | #desc = compiler_result.to_desc() 26 | 27 | # Load desc instead: 28 | with gzip.open('./test/res/desc/P2GenericECDSAPubKey_desc.json.gz', 'r') as f: 29 | desc = json.load(f) 30 | 31 | type_classes = build_type_classes(desc) 32 | Point = type_classes['Point'] 33 | 34 | P2PK = build_contract_class(desc) 35 | x, y = key_pub.to_point() 36 | p2pk = P2PK(Point({'x': x, 'y': y})) 37 | 38 | context = create_dummy_input_context() 39 | context.utxo.script_pubkey = p2pk.locking_script 40 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 41 | preimage = get_preimage_from_input_context(context, sighash_flag) 42 | 43 | ### Derive proof: 44 | r = PrivateKey.from_arbitrary_bytes(b'123test321') 45 | #r = PrivateKey.from_random() 46 | 47 | A = r.public_key 48 | G = PrivateKey.from_int(1).public_key 49 | st = G.to_bytes(compressed=False) + key_pub.to_bytes(compressed=False) 50 | e = int.from_bytes(sha256(preimage + st + A.to_bytes(compressed=False)), 51 | byteorder='little') % 2**128 52 | e = PrivateKey.from_int(e) 53 | z = key_priv.multiply(e._secret).add(r._secret) 54 | 55 | 56 | # Verify proof off-chain: 57 | def test_verfy_off_chain_correct(): 58 | zG = z.public_key 59 | ePK = key_pub.multiply(e._secret) 60 | ePKx, ePKy = ePK.to_point() 61 | ePK_neg = PublicKey.from_point(ePKx, (ePKy * -1) % p) 62 | A = PublicKey.combine_keys([zG, ePK_neg]) 63 | 64 | st = G.to_bytes(compressed=False) + key_pub.to_bytes(compressed=False) 65 | bA = A.to_bytes(compressed=False) 66 | _e = int.from_bytes(sha256(preimage + st + bA), byteorder='little') % 2**128 67 | assert(_e == e.to_int()) 68 | 69 | 70 | # Verify proof on-chain: 71 | def test_verfy_on_chain_correct(): 72 | assert p2pk.unlock(e.to_int(), z.to_int(), SigHashPreimage(preimage)).verify(context) 73 | 74 | def test_verfy_on_chain_wrong(): 75 | with pytest.raises(NullFailError): 76 | assert p2pk.unlock(e.to_int() - 1, z.to_int(), SigHashPreimage(preimage)).verify(context) 77 | 78 | 79 | -------------------------------------------------------------------------------- /test/test_contract_token_manual_state.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script 9 | 10 | 11 | key_priv_0 = PrivateKey.from_arbitrary_bytes(b'test123') 12 | key_pub_0 = key_priv_0.public_key 13 | key_priv_1 = PrivateKey.from_arbitrary_bytes(b'123test') 14 | key_pub_1 = key_priv_1.public_key 15 | 16 | contract = './test/res/tokenManualState.scrypt' 17 | 18 | compiler_result = scryptlib.utils.compile_contract(contract) 19 | desc = compiler_result.to_desc() 20 | 21 | Token = scryptlib.contract.build_contract_class(desc) 22 | 23 | token = Token() 24 | token.set_data_part(key_pub_0.to_bytes() + scryptlib.utils.get_push_int(100)[1:] 25 | + key_pub_1.to_bytes() + scryptlib.utils.get_push_int(0)[1:]) 26 | 27 | # Create context and set prev locking script. 28 | context = scryptlib.utils.create_dummy_input_context() 29 | context.utxo.script_pubkey = token.locking_script 30 | 31 | # Create new locking script. 32 | new_data_part = key_pub_0.to_bytes() + scryptlib.utils.get_push_int(60)[1:] \ 33 | + key_pub_1.to_bytes() + scryptlib.utils.get_push_int(40)[1:] 34 | new_locking_script = Script(token.code_part.to_bytes() + new_data_part) 35 | tx_out = TxOutput(value=222222, script_pubkey=new_locking_script) 36 | context.tx.outputs.append(tx_out) 37 | 38 | # Create signature. 39 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 40 | input_idx = 0 41 | utxo_satoshis = context.utxo.value 42 | sighash = context.tx.signature_hash(input_idx, utxo_satoshis, token.locking_script, sighash_flag) 43 | sig = key_priv_0.sign(sighash, hasher=None) 44 | sig = sig + pack_byte(sighash_flag) 45 | 46 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 47 | 48 | 49 | def test_verify_correct(): 50 | verify_result = token.transfer(PubKey(key_pub_0), 51 | Sig(sig), 52 | PubKey(key_pub_1), 53 | 40, 54 | SigHashPreimage(preimage), 55 | 222222).verify(context) 56 | assert verify_result == True 57 | 58 | 59 | def test_verify_wrong_val(): 60 | verify_result = token.transfer(PubKey(key_pub_0), 61 | Sig(sig), 62 | PubKey(key_pub_1), 63 | 40, 64 | SigHashPreimage(preimage), 65 | 221222).verify(context) 66 | assert verify_result == False 67 | 68 | 69 | def test_verify_wrong_val_2(): 70 | verify_result = token.transfer(PubKey(key_pub_0), 71 | Sig(sig), 72 | PubKey(key_pub_1), 73 | 43, 74 | SigHashPreimage(preimage), 75 | 222222).verify(context) 76 | assert verify_result == False 77 | -------------------------------------------------------------------------------- /test/test_contract_nested_struct_arr.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib 4 | from scryptlib.types import * 5 | 6 | 7 | contract = ''' 8 | struct Thing { 9 | int someNumber; 10 | OtherThing[2] otherThings; 11 | } 12 | 13 | struct OtherThing { 14 | bytes someBytes; 15 | int[3] numbers; 16 | } 17 | 18 | contract NestedStructs { 19 | Thing thing; 20 | 21 | public function unlock(int[2][3] numbers, int someNumber, bytes[2] someBytes) { 22 | loop (2) : i { 23 | auto otherThing = this.thing.otherThings[i]; 24 | require(otherThing.someBytes == someBytes[i]); 25 | loop (3) : j { 26 | require(otherThing.numbers[j] == numbers[i][j]); 27 | } 28 | } 29 | require(this.thing.someNumber == someNumber); 30 | } 31 | } 32 | ''' 33 | 34 | compiler_result = scryptlib.utils.compile_contract(contract, from_string=True) 35 | desc = compiler_result.to_desc() 36 | 37 | NestedStructs = scryptlib.build_contract_class(desc) 38 | type_classes = scryptlib.build_type_classes(desc) 39 | 40 | Thing = type_classes['Thing'] 41 | OtherThing = type_classes['OtherThing'] 42 | 43 | other_things = [] 44 | for i in range(2): 45 | other_things.append(OtherThing({ 46 | 'someBytes': Bytes('000000'), 47 | 'numbers': [Int(0), Int(0), Int(0)], 48 | })) 49 | 50 | thing = Thing({ 51 | 'someNumber': Int(123), 52 | 'otherThings': other_things, 53 | }) 54 | 55 | nested_structs = NestedStructs(thing) 56 | 57 | def test_verify_correct(): 58 | numbers = [[0, 0, 0], [0, 0, 0]] 59 | some_number = 123 60 | some_bytes = [b'\x00\x00\x00', b'\x00\x00\x00'] 61 | 62 | verify_result = nested_structs.unlock(numbers, some_number, some_bytes).verify() 63 | assert verify_result == True 64 | 65 | 66 | def test_verify_wrong(): 67 | numbers = [[0, 2, 0], [0, 0, 0]] 68 | some_number = 123 69 | some_bytes = [b'\x00\x00\x00', b'\x00\x00\x00'] 70 | 71 | with pytest.raises(bitcoinx.VerifyFailed): 72 | nested_structs.unlock(numbers, some_number, some_bytes).verify() 73 | 74 | numbers = [[0, 0, 0], [0, 0, 0]] 75 | some_number = 124 76 | some_bytes = [b'\x00\x00\x00', b'\x00\x00\x00'] 77 | 78 | verify_result = nested_structs.unlock(numbers, some_number, some_bytes).verify() 79 | assert verify_result == False 80 | 81 | numbers = [[0, 0, 0], [0, 0, 0]] 82 | some_number = 123 83 | some_bytes = [b'\x01\x00\x00', b'\x00\x00\x00'] 84 | 85 | with pytest.raises(bitcoinx.VerifyFailed): 86 | nested_structs.unlock(numbers, some_number, some_bytes).verify() 87 | -------------------------------------------------------------------------------- /test/test_contract_state_map.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from bitcoinx import TxOutput 4 | 5 | import scryptlib 6 | from scryptlib.types import * 7 | 8 | contract = './test/res/stateMap.scrypt' 9 | compiler_result = scryptlib.utils.compile_contract(contract) 10 | desc = compiler_result.to_desc() 11 | 12 | StateMap = scryptlib.build_contract_class(desc) 13 | MapEntry = scryptlib.contract.build_type_classes(desc)['MapEntry'] 14 | 15 | # Initialize with empty bytes. 16 | state_map = StateMap(Bytes(b'')) 17 | 18 | hm = HashedMap(Int, Int) 19 | 20 | 21 | def get_context(hashed_map): 22 | new_ls = state_map.get_state_script({'_mpData': Bytes(hashed_map.hex)}) 23 | 24 | context = scryptlib.create_dummy_input_context() 25 | context.utxo.script_pubkey = state_map.locking_script 26 | 27 | tx_out = TxOutput(value=0, script_pubkey=new_ls) 28 | context.tx.outputs.append(tx_out) 29 | 30 | return context 31 | 32 | 33 | def test_verify_insert(): 34 | kv_pairs = [(Int(3), Int(1)), 35 | (Int(5), Int(6)), 36 | (Int(0), Int(11)), 37 | (Int(1), Int(5))] 38 | 39 | for key, val in kv_pairs: 40 | hm.set(key, val) 41 | context = get_context(hm) 42 | preimage = scryptlib.get_preimage_from_input_context(context) 43 | 44 | map_entry = MapEntry({ 45 | 'key': key, 46 | 'val': val, 47 | 'keyIndex': hm.key_index(key)}) 48 | verfiy_result = state_map.insert(map_entry, SigHashPreimage(preimage)).verify(context) 49 | assert verfiy_result == True 50 | 51 | state_map._mpData = Bytes(hm.hex) 52 | 53 | 54 | def test_verify_update(): 55 | kv_pairs = [(Int(1), Int(6)), 56 | (Int(1), Int(8)), 57 | (Int(0), Int(1))] 58 | 59 | for key, val in kv_pairs: 60 | hm.set(key, val) 61 | context = get_context(hm) 62 | preimage = scryptlib.get_preimage_from_input_context(context) 63 | 64 | map_entry = MapEntry({ 65 | 'key': key, 66 | 'val': val, 67 | 'keyIndex': hm.key_index(key)}) 68 | verfiy_result = state_map.update(map_entry, SigHashPreimage(preimage)).verify(context) 69 | assert verfiy_result == True 70 | 71 | state_map._mpData = Bytes(hm.hex) 72 | 73 | 74 | def test_verify_delete(): 75 | keys = [1, 5, 3, 0] 76 | 77 | for key in keys: 78 | key_index = hm.key_index(key) 79 | hm.delete(key) 80 | 81 | context = get_context(hm) 82 | preimage = scryptlib.get_preimage_from_input_context(context) 83 | 84 | verfiy_result = state_map.delete(key, key_index, SigHashPreimage(preimage)).verify(context) 85 | assert verfiy_result == True 86 | 87 | state_map._mpData = Bytes(hm.hex) 88 | 89 | -------------------------------------------------------------------------------- /test/test_contract_advanced_counter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import SigHashPreimage, Int, Ripemd160 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, TxOutput, Script, PrivateKey, P2PKH_Address, Bitcoin 9 | 10 | 11 | COUNTER_INITIAL_VAL = 0 12 | out_sats = 22222 13 | change_sats = 11111 14 | 15 | key_priv_0 = PrivateKey.from_arbitrary_bytes(b'test123') 16 | key_pub_0 = key_priv_0.public_key 17 | pkh_0 = key_pub_0.hash160() 18 | 19 | contract = './test/res/advancedCounter.scrypt' 20 | 21 | compiler_result = scryptlib.utils.compile_contract(contract) 22 | desc = compiler_result.to_desc() 23 | 24 | Counter = scryptlib.contract.build_contract_class(desc) 25 | counter = Counter() 26 | 27 | counter.set_data_part(scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL)) 28 | 29 | context = scryptlib.utils.create_dummy_input_context() 30 | context.utxo.script_pubkey = counter.locking_script 31 | 32 | subsequent_counter_val_bytes = scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL + 1) 33 | new_locking_script = counter.code_part << Script(subsequent_counter_val_bytes) 34 | 35 | # Counter output 36 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 37 | context.tx.outputs.append(tx_out) 38 | 39 | # Change output 40 | pkh_0_out = TxOutput(change_sats, P2PKH_Address(pkh_0, Bitcoin).to_script()) 41 | context.tx.outputs.append(pkh_0_out) 42 | 43 | sighash_flag = SigHash(SigHash.ANYONE_CAN_PAY | SigHash.ALL | SigHash.FORKID) 44 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 45 | 46 | def test_verify_correct(): 47 | verify_result = counter.increment(SigHashPreimage(preimage), out_sats, Ripemd160(pkh_0), change_sats).verify(context) 48 | assert verify_result == True 49 | 50 | 51 | def test_verify_wrong_sats(): 52 | verify_result = counter.increment(SigHashPreimage(preimage), out_sats + 1, Ripemd160(pkh_0), change_sats).verify(context) 53 | assert verify_result == False 54 | 55 | 56 | def test_verify_wrong_sats2(): 57 | verify_result = counter.increment(SigHashPreimage(preimage), out_sats, Ripemd160(pkh_0), change_sats - 1).verify(context) 58 | assert verify_result == False 59 | 60 | 61 | def test_verify_wrong_nextval(): 62 | subsequent_counter_val_bytes = scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL + 2) 63 | new_locking_script = counter.code_part << Script(subsequent_counter_val_bytes) 64 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 65 | context.tx.outputs[0] = tx_out 66 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 67 | 68 | verify_result = counter.increment(SigHashPreimage(preimage), out_sats, Ripemd160(pkh_0), change_sats - 1).verify(context) 69 | assert verify_result == False 70 | 71 | -------------------------------------------------------------------------------- /test/res/deadMansSwitch.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * Dead man's switch / last will contract. 5 | */ 6 | 7 | contract DeadMansSwitch { 8 | 9 | @state 10 | PubKey pubKeyRefresh; 11 | 12 | @state 13 | PubKey pubKeyCancel; 14 | 15 | @state 16 | PubKey pubKeyInheritor; 17 | 18 | @state 19 | int refreshTimestamp; 20 | 21 | @state 22 | int inheritTimestampOffset; 23 | 24 | @state 25 | int inheritanceSats; 26 | 27 | public function refresh(SigHashPreimage txPreimage, int currentTime, Sig sig) { 28 | require(Tx.checkPreimage(txPreimage)); 29 | 30 | require(checkSig(sig, pubKeyRefresh)); 31 | 32 | this.refreshTimestamp = refreshTimestamp 33 | 34 | bytes outputScript = this.getStateScript(); 35 | bytes output = Util.buildOutput(outputScript, inheritanceSats); 36 | require(hash256(output) == Util.hashOutputs(txPreimage)); 37 | } 38 | 39 | public function cancel(Sig sig) { 40 | 41 | } 42 | 43 | public function inherit(SigHashPreimage txPreimage, Sig sig) { 44 | require(Tx.checkPreimage(txPreimage)); 45 | 46 | require(checkSig(sig, pubKeyInheritor)); 47 | 48 | // This function call should only be spendable after the the last refresh + offset has passed. 49 | require(Util.nLocktime(txPreimage) >= this.refreshTimestamp + this.inheritTimestampOffset); 50 | 51 | // Also check nSequence 52 | } 53 | 54 | } 55 | 56 | // TODO: Do one without nlocktime and the owner as the time oracle 57 | import "util.scrypt"; 58 | 59 | /** 60 | * Dead man's switch / last will contract. 61 | */ 62 | 63 | contract DeadMansSwitch { 64 | 65 | @state 66 | PubKey pubKeyRefresh; 67 | 68 | @state 69 | PubKey pubKeyCancel; 70 | 71 | @state 72 | PubKey pubKeyInheritor; 73 | 74 | /** 75 | * Unix timestamp past which the inheritor can withdraw the inherited funds. 76 | * This value gets updated each time the contract is refreshed by the owner. 77 | */ 78 | @state 79 | int inheritTimestamp; 80 | 81 | @state 82 | int inheritanceSats; 83 | 84 | public function refresh(SigHashPreimage txPreimage, int newInheritTimestamp, Sig sig) { 85 | require(Tx.checkPreimage(txPreimage)); 86 | 87 | require(checkSig(sig, pubKeyRefresh)); 88 | 89 | this.inheritNLocktime = newInheritTimestamp 90 | 91 | // Make sure next iteration includes updated nLocktime. 92 | require(newNLocktime == Util.nLocktime(txPreimage)); 93 | 94 | 95 | } 96 | 97 | public function cancel(Sig sig) { 98 | 99 | } 100 | 101 | public function inherit(SigHashPreimage txPreimage, Sig sig) { 102 | 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /test/res/escrow.scrypt: -------------------------------------------------------------------------------- 1 | /** 2 | A simple Bitcoin escrow contract 3 | An amount X is locked with a transaction. 4 | We have three scenarios which can unlock the script. 5 | A: Alice, B: Bob, E: Escrow, P: Public Key 6 | Scenario 1: PA + PB 7 | Alice and Bob have to sign. There will be two new outputs (50/50). 8 | Scenario 2: PA + PE + Hash 1 9 | Alice and Escrow have to sign and use the secret 1. Alice gets the output. 10 | Scenario 3: PB + PE + Hash 2 11 | Bob and Escrow have to sign and use the secret 2. Bob gets the output. 12 | **/ 13 | 14 | import "util.scrypt"; 15 | 16 | contract SimpleEscrow { 17 | Ripemd160 pubKeyHashA; 18 | Ripemd160 pubKeyHashB; 19 | Ripemd160 pubKeyHashE; 20 | Sha256 hash1; 21 | Sha256 hash2; 22 | int fee; 23 | 24 | function split(int amount, SigHashPreimage txPreimage) : bool { 25 | // split amount, both new outputs get the same amount 26 | bytes scriptA = Util.buildPublicKeyHashScript(this.pubKeyHashA); 27 | bytes outputA = Util.buildOutput(scriptA, amount / 2 - this.fee); 28 | 29 | bytes scriptB = Util.buildPublicKeyHashScript(this.pubKeyHashB); 30 | bytes outputB = Util.buildOutput(scriptB, amount / 2 - this.fee); 31 | 32 | bytes outputs = outputA + outputB; 33 | 34 | return (hash256(outputs) == Util.hashOutputs(txPreimage)); 35 | } 36 | 37 | function spend(int amount, SigHashPreimage txPreimage, Ripemd160 pubKeyHash) : bool { 38 | bytes script = Util.buildPublicKeyHashScript(pubKeyHash); 39 | bytes output = Util.buildOutput(script, amount - this.fee); 40 | 41 | return (hash256(output) == Util.hashOutputs(txPreimage)); 42 | } 43 | 44 | public function unlock(SigHashPreimage txPreimage, PubKey pubKey1, Sig sig1, PubKey pubKey2, Sig sig2, bytes secret) { 45 | bool status = false; 46 | 47 | require(Tx.checkPreimage(txPreimage)); 48 | int amount = Util.value(txPreimage); 49 | 50 | require(checkSig(sig1, pubKey1)); 51 | require(checkSig(sig2, pubKey2)); 52 | 53 | if (hash160(pubKey1) == this.pubKeyHashA) { 54 | if (hash160(pubKey2) == this.pubKeyHashB) { 55 | require(this.split(amount, txPreimage)); 56 | status = true; 57 | } else if (hash160(pubKey2) == this.pubKeyHashE) { 58 | require(sha256(secret) == this.hash1); 59 | require(this.spend(amount, txPreimage, this.pubKeyHashA)); 60 | status = true; 61 | } 62 | } else if (hash160(pubKey1) == this.pubKeyHashB) { 63 | if (hash160(pubKey2) == this.pubKeyHashE) { 64 | require(sha256(secret) == this.hash2); 65 | require(this.spend(amount, txPreimage, this.pubKeyHashB)); 66 | status = true; 67 | } 68 | } 69 | 70 | require(status); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/test_contract_serializer.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import random 3 | import os 4 | 5 | import scryptlib.utils 6 | import scryptlib.contract 7 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage, Bool, Bytes 8 | 9 | import bitcoinx 10 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, sha256 11 | 12 | 13 | contract = './test/res/serializer.scrypt' 14 | 15 | compiler_result = scryptlib.utils.compile_contract(contract) 16 | desc = compiler_result.to_desc() 17 | 18 | Demo = scryptlib.contract.build_contract_class(desc) 19 | demo = Demo() 20 | 21 | def test_verify_bool(): 22 | verify_result = demo.testBool(True).verify() 23 | assert verify_result == True 24 | 25 | verify_result = demo.testBool(False).verify() 26 | assert verify_result == True 27 | 28 | verify_result = demo.testBool(Bool(True)).verify() 29 | assert verify_result == True 30 | 31 | verify_result = demo.testBool(Bool(False)).verify() 32 | assert verify_result == True 33 | 34 | 35 | def test_verify_special_int(): 36 | verify_result = demo.testInt(0).verify() 37 | assert verify_result == True 38 | 39 | verify_result = demo.testInt(-1).verify() 40 | assert verify_result == True 41 | 42 | 43 | def test_verify_normal_int(): 44 | for num in [1, 0x0a, 100, -1000, 12983128039190833298]: 45 | verify_result = demo.testInt(num).verify() 46 | assert verify_result == True 47 | 48 | for num in [1, 0x0a, 100, -1000, 12983128039190833298]: 49 | verify_result = demo.testInt(Int(num)).verify() 50 | assert verify_result == True 51 | 52 | 53 | def test_verify_bytes(): 54 | verify_result = demo.testBytes(Bytes('1100')).verify() 55 | assert verify_result == True 56 | 57 | verify_result = demo.testBytes(Bytes('1100ffff')).verify() 58 | assert verify_result == True 59 | 60 | 61 | def test_verify_pushdata_1(): 62 | verify_result = demo.testBytes(Bytes(b'\x11' * 76)).verify() 63 | assert verify_result == True 64 | 65 | verify_result = demo.testBytes(Bytes(b'\xff' * (0x100 - 1))).verify() 66 | assert verify_result == True 67 | 68 | 69 | def test_verify_pushdata_2(): 70 | verify_result = demo.testBytes(Bytes(b'\x11' * (2**8))).verify() 71 | assert verify_result == True 72 | 73 | 74 | #def test_verify_pushdata_4(): 75 | # verify_result = demo.testBytes(Bytes(b'\x11' * (2**16))).verify() 76 | # assert verify_result == True 77 | 78 | 79 | def test_verify_main(): 80 | bounds = [0x0, 0xfc, 0xffff] 81 | for i, bound in enumerate(bounds[:-1]): 82 | for j in range(0, 10): 83 | n = random.randint(0, 2**32) 84 | m = random.randint(bound, bounds[i+1]) 85 | #h = random.randbytes(m) 86 | h = os.urandom(m) 87 | 88 | verify_result = demo.main(n % 2 == 0, Bytes(h), n).verify() 89 | assert verify_result == True 90 | 91 | -------------------------------------------------------------------------------- /test/test_contract_advanced_token_sale.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, Ripemd160, SigHashPreimage, Bytes 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, PrivateKey, P2PKH_Address, TxOutput, Bitcoin, Tx, TxInput, TxInputContext, Script, PublicKey 9 | 10 | 11 | 12 | SATS_PER_TOKEN = 1000 13 | input_sats = 100000 14 | sighash_flag = SigHash(SigHash.ANYONE_CAN_PAY | SigHash.ALL | SigHash.FORKID) 15 | 16 | 17 | priv_keys = [] 18 | for i in range(0, 5): 19 | priv_keys.append(PrivateKey.from_random()) 20 | 21 | pub_keys = [] 22 | for priv_key in priv_keys: 23 | pub_keys.append(priv_key.public_key) 24 | 25 | pkhs = [] 26 | for pub_key in pub_keys: 27 | pkhs.append(pub_key.hash160()) 28 | 29 | 30 | contract = './test/res/advancedTokenSale.scrypt' 31 | 32 | compiler_result = scryptlib.utils.compile_contract(contract) 33 | desc = compiler_result.to_desc() 34 | 35 | AdvancedTokenSale = scryptlib.contract.build_contract_class(desc) 36 | ats = AdvancedTokenSale(SATS_PER_TOKEN) 37 | 38 | # Add "empty" public key 39 | empt_pub_key = scryptlib.utils.get_push_item(bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000000000')) 40 | ats.set_data_part(empt_pub_key) 41 | 42 | 43 | def sale(n_bought, pkh, pub_key): 44 | context = scryptlib.utils.create_dummy_input_context() 45 | context.utxo.script_pubkey = ats.locking_script 46 | context.utxo.value = input_sats 47 | 48 | new_data_part = ats.data_part << pub_key.to_bytes() + scryptlib.utils.get_push_int(n_bought)[1:] 49 | new_locking_script = Script(ats.code_part.to_bytes() + new_data_part.to_bytes()) 50 | 51 | change_sats = input_sats - n_bought * SATS_PER_TOKEN 52 | out_sats = input_sats + n_bought * SATS_PER_TOKEN 53 | 54 | # Counter output 55 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 56 | context.tx.outputs.append(tx_out) 57 | 58 | # Change output 59 | change_out = TxOutput(change_sats, P2PKH_Address(pub_key.hash160(), Bitcoin).to_script()) 60 | context.tx.outputs.append(change_out) 61 | 62 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 63 | 64 | verify_result = ats.buy(SigHashPreimage(preimage), Ripemd160(pkh), change_sats, Bytes(pub_key.to_bytes()), n_bought).verify(context) 65 | assert verify_result == True 66 | 67 | return new_data_part 68 | 69 | 70 | def test_verify_correct_sales(): 71 | new_data_part = sale(1, pkhs[0], pub_keys[0]) 72 | 73 | ats.set_data_part(new_data_part.to_bytes()) 74 | new_data_part = sale(3, pkhs[1], pub_keys[1]) 75 | 76 | ats.set_data_part(new_data_part.to_bytes()) 77 | new_data_part = sale(10, pkhs[2], pub_keys[2]) 78 | 79 | ats.set_data_part(new_data_part.to_bytes()) 80 | new_data_part = sale(2, pkhs[3], pub_keys[3]) 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /test/test_contract_merkle_token.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import hashlib 3 | 4 | import scryptlib.utils 5 | import scryptlib.contract 6 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage, Ripemd160, Bytes 7 | 8 | import bitcoinx 9 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, TxInput, P2PKH_Address, Bitcoin 10 | 11 | 12 | key_priv = PrivateKey.from_arbitrary_bytes(b'test0') 13 | key_pub = key_priv.public_key 14 | pkh = key_pub.hash160() 15 | payout_addr = pkh 16 | change_addr = pkh 17 | 18 | contract = './test/res/merkleToken.scrypt' 19 | 20 | compiler_result = scryptlib.utils.compile_contract(contract) 21 | desc = compiler_result.to_desc() 22 | 23 | input_sats = 100000 24 | sat_price = 100 25 | Token = scryptlib.contract.build_contract_class(desc) 26 | token = Token(sat_price) 27 | 28 | change_sats = 100 29 | 30 | sighash_flag = SigHash(SigHash.ANYONE_CAN_PAY | SigHash.ALL | SigHash.FORKID) 31 | 32 | 33 | #def test_verify_buy_token(): 34 | # amount = 1 35 | # new_entry = payout_addr + scryptlib.utils.get_push_int(amount)[1:] 36 | # last_entry = b'\x00' * 20 + b'\x01' 37 | # 38 | # last_entry_hash = hashlib.sha256(last_entry).digest() 39 | # new_entry_hash = hashlib.sha256(new_entry).digest() 40 | # 41 | # mixhash = hashlib.sha256(last_entry_hash + new_entry_hash).digest() 42 | # new_locking_script = token.code_part << Script(b'\x23' + scryptlib.utils.get_push_item(mixhash)) 43 | # last_merkle_path = Bytes(last_entry_hash + b'\x01') 44 | # 45 | # last_entry_double_hash = hashlib.sha256(last_entry_hash * 2).digest() 46 | # token.set_data_part(scryptlib.utils.get_push_item(last_entry_double_hash)) 47 | # 48 | # context = scryptlib.utils.create_dummy_input_context() 49 | # context.utxo.script_pubkey = token.locking_script 50 | # context.utxo.value = input_sats 51 | # 52 | # #dummy_prev_hash = bytes.fromhex('a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458') 53 | # #context.tx.inputs.append(TxInput(dummy_prev_hash, 0, Script(), 0xffffffff)) 54 | # 55 | # # Token output 56 | # change_out = TxOutput(input_sats + sat_price * amount, new_locking_script) 57 | # context.tx.outputs.append(change_out) 58 | # 59 | # # Change output 60 | # change_out = TxOutput(change_sats, P2PKH_Address(change_addr, Bitcoin).to_script()) 61 | # context.tx.outputs.append(change_out) 62 | # 63 | # sighash_flag = SigHash(SigHash.ANYONE_CAN_PAY | SigHash.ALL | SigHash.FORKID) 64 | # preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 65 | # 66 | # verify_result = token.buy(SigHashPreimage(preimage), 67 | # amount, 68 | # Ripemd160(change_addr), 69 | # Ripemd160(payout_addr), 70 | # change_sats, 71 | # Bytes(last_entry), 72 | # last_merkle_path 73 | # ).verify(context) 74 | # assert verify_result == True 75 | 76 | 77 | -------------------------------------------------------------------------------- /test/res/conwaygol.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | // Conway Game Of Life on a board of N * N 4 | contract GameOfLife { 5 | static const int N = 5; 6 | // effctively we play on a grid of (N+2) * (N+2) without handling boundary cells 7 | static int BOARD_SIZE = GameOfLife.N + 2; 8 | static bytes LIVE = b'01'; 9 | static bytes DEAD = b'00'; 10 | static const int LOOP_NEIGHBORS = 3; 11 | 12 | public function play(int amount, SigHashPreimage txPreimage) { 13 | require(Tx.checkPreimage(txPreimage)); 14 | 15 | bytes scriptCode = Util.scriptCode(txPreimage); 16 | int scriptLen = len(scriptCode); 17 | 18 | int BOARDLEN = GameOfLife.BOARD_SIZE * GameOfLife.BOARD_SIZE; 19 | int boardStart = scriptLen - BOARDLEN; 20 | bytes oldBoard = scriptCode[boardStart :]; 21 | 22 | // make the move 23 | bytes newBoard = this.evolve(oldBoard); 24 | 25 | // update state: next turn & next board 26 | bytes scriptCode_ = scriptCode[: scriptLen - BOARDLEN] + newBoard; 27 | bytes output = Util.buildOutput(scriptCode_, amount); 28 | bytes outputs = output; 29 | 30 | require(hash256(outputs) == Util.hashOutputs(txPreimage)); 31 | } 32 | 33 | function evolve(bytes oldBoard) : bytes { 34 | bytes newBoard = oldBoard; 35 | 36 | int i = 1; 37 | loop (GameOfLife.N) { 38 | int j = 1; 39 | loop (GameOfLife.N) { 40 | bytes nextState = this.next(oldBoard, i, j); 41 | newBoard = Util.setElemAt(newBoard, this.index(i, j), nextState); 42 | 43 | j++; 44 | } 45 | 46 | i++; 47 | } 48 | 49 | return newBoard; 50 | } 51 | 52 | function next(bytes oldBoard, int row, int col) : bytes { 53 | // number of neighbors alive 54 | int alive = 0; 55 | 56 | int i = -1; 57 | loop (LOOP_NEIGHBORS) { 58 | int j = -1; 59 | 60 | loop (LOOP_NEIGHBORS) { 61 | if (!(i == 0 && j == 0)) { 62 | if (Util.getElemAt(oldBoard, this.index(row + i, col + j))) { 63 | alive++; 64 | } 65 | } 66 | j++; 67 | } 68 | 69 | i++; 70 | } 71 | 72 | bytes oldState = Util.getElemAt(oldBoard, this.index(row, col)); 73 | /* rule 74 | 1. Any live cell with two or three live neighbours survives. 75 | 2. Any dead cell with three live neighbours becomes a live cell. 76 | 3. All other live cells die in the next generation. Similarly, all other dead cells stay dead. 77 | */ 78 | return (alive == 3 || alive == 2 && oldState == LIVE) ? LIVE : DEAD; 79 | } 80 | 81 | function index(int i, int j) : int { 82 | return i * GameOfLife.BOARD_SIZE + j; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/test_contract_acs.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Sig, PubKey, Ripemd160, SigHashPreimage 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, PrivateKey, P2PKH_Address, TxOutput, Bitcoin, Tx, TxInput, TxInputContext, Script 9 | 10 | 11 | def create_input_context(utxo_satoshis, utxo_locking_script, new_out): 12 | tx_version = 2 13 | tx_locktime = 0x00000000 14 | 15 | utxo = TxOutput(utxo_satoshis, utxo_locking_script) 16 | prev_tx = Tx(tx_version, [], [utxo], tx_locktime) 17 | prev_txid = prev_tx.hash() 18 | 19 | utxo_idx = 0 20 | n_sequence = 0xffffffff 21 | unlocking_script = Script() 22 | curr_in = TxInput(prev_txid, utxo_idx, unlocking_script, n_sequence) 23 | curr_tx = Tx(tx_version, [curr_in], [new_out], tx_locktime) 24 | 25 | input_idx = 0 26 | return TxInputContext(curr_tx, input_idx, utxo, is_utxo_after_genesis=True) 27 | 28 | 29 | in_sats = 1200 30 | miner_fee = 546 31 | 32 | key_priv_0 = PrivateKey.from_arbitrary_bytes(b'test123') 33 | key_pub_0 = key_priv_0.public_key 34 | pkh_0 = key_pub_0.hash160() 35 | 36 | contract = './test/res/acs.scrypt' 37 | 38 | compiler_result = scryptlib.utils.compile_contract(contract) 39 | desc = compiler_result.to_desc() 40 | 41 | AnyoneCanSpend = scryptlib.contract.build_contract_class(desc) 42 | acs = AnyoneCanSpend(Ripemd160(pkh_0)) 43 | 44 | pkh_0_out = TxOutput(in_sats - miner_fee, P2PKH_Address(pkh_0, Bitcoin).to_script()) 45 | context = create_input_context(in_sats, acs.locking_script, pkh_0_out) 46 | 47 | sighash_flag = SigHash(SigHash.ANYONE_CAN_PAY | SigHash.ALL | SigHash.FORKID) 48 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 49 | 50 | 51 | def test_verify_correct(): 52 | verify_result = acs.unlock(SigHashPreimage(preimage)).verify(context) 53 | assert verify_result == True 54 | 55 | 56 | def test_verify_wrong_addr(): 57 | key_priv = PrivateKey.from_arbitrary_bytes(b'123test') 58 | key_pub = key_priv.public_key 59 | pkh = key_pub.hash160() 60 | pkh_0_out_wrong = TxOutput(in_sats - miner_fee, P2PKH_Address(pkh, Bitcoin).to_script()) 61 | context = create_input_context(in_sats, acs.locking_script, pkh_0_out_wrong) 62 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 63 | verify_result = acs.unlock(SigHashPreimage(preimage)).verify(context) 64 | assert verify_result == False 65 | 66 | 67 | def test_verify_wrong_out_amount(): 68 | pkh_0_out_wrong = TxOutput(in_sats - miner_fee + 123, P2PKH_Address(pkh_0, Bitcoin).to_script()) 69 | context = create_input_context(in_sats, acs.locking_script, pkh_0_out_wrong) 70 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 71 | verify_result = acs.unlock(SigHashPreimage(preimage)).verify(context) 72 | assert verify_result == False 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /test/test_contract_counter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import SigHashPreimage, Int 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, TxOutput, Script 9 | 10 | 11 | COUNTER_INITIAL_VAL = 0 12 | 13 | contract = './test/res/counter.scrypt' 14 | 15 | compiler_result = scryptlib.utils.compile_contract(contract) 16 | desc = compiler_result.to_desc() 17 | 18 | Counter = scryptlib.contract.build_contract_class(desc) 19 | counter_obj = Counter() 20 | 21 | # Set initial coutner value as OP_RETURN data in the locking script. 22 | counter_obj.set_data_part(scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL)) 23 | 24 | 25 | def test_verify_correct(): 26 | context = scryptlib.utils.create_dummy_input_context() 27 | context.utxo.script_pubkey = counter_obj.locking_script 28 | 29 | subsequent_counter_val_bytes = scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL + 1) 30 | new_locking_script = counter_obj.code_part << Script(subsequent_counter_val_bytes) 31 | tx_out = TxOutput(value=0, script_pubkey=new_locking_script) 32 | context.tx.outputs.append(tx_out) 33 | 34 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 35 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 36 | new_output_satoshis = 0 37 | verify_result = counter_obj.increment(SigHashPreimage(preimage), Int(new_output_satoshis)).verify(context) 38 | assert verify_result == True 39 | 40 | 41 | def test_verify_incorrect_increment(): 42 | context = scryptlib.utils.create_dummy_input_context() 43 | context.utxo.script_pubkey = counter_obj.locking_script 44 | 45 | subsequent_counter_val_bytes = scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL + 2) 46 | new_locking_script = counter_obj.code_part << Script(subsequent_counter_val_bytes) 47 | tx_out = TxOutput(value=0, script_pubkey=new_locking_script) 48 | context.tx.outputs.append(tx_out) 49 | 50 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 51 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 52 | new_output_satoshis = 0 53 | verify_result = counter_obj.increment(SigHashPreimage(preimage), Int(new_output_satoshis)).verify(context) 54 | assert verify_result == False 55 | 56 | 57 | def test_verify_incorrect_sat_amount(): 58 | context = scryptlib.utils.create_dummy_input_context() 59 | context.utxo.script_pubkey = counter_obj.locking_script 60 | 61 | subsequent_counter_val_bytes = scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL + 1) 62 | new_locking_script = counter_obj.code_part << Script(subsequent_counter_val_bytes) 63 | tx_out = TxOutput(value=0, script_pubkey=new_locking_script) 64 | context.tx.outputs.append(tx_out) 65 | 66 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 67 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 68 | new_output_satoshis = 100 69 | verify_result = counter_obj.increment(SigHashPreimage(preimage), Int(new_output_satoshis)).verify(context) 70 | assert verify_result == False 71 | 72 | -------------------------------------------------------------------------------- /test/res/nonFungibleToken.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * A non-fungible token protocol based on UTXO model 5 | */ 6 | contract NonFungibleToken { 7 | static const bytes ISSUE = b'00'; 8 | static const bytes TRANSFER = b'01'; 9 | 10 | public function issue(Sig issuerSig, PubKey receiver, int satoshiAmount0, int satoshiAmount1, SigHashPreimage txPreimage) { 11 | // this ensures the preimage is for the current tx 12 | require(Tx.checkPreimage(txPreimage)); 13 | 14 | // read previous locking script: codePart + OP_RETURN + currTokenId + issuer + matchAction 15 | bytes lockingScript = Util.scriptCode(txPreimage); 16 | int scriptLen = len(lockingScript); 17 | 18 | // constant part of locking script: upto op_return 19 | int constStart = scriptLen - Util.DataLen - Util.PubKeyLen - 1; 20 | bytes constPart = lockingScript[: constStart]; 21 | 22 | bytes matchAction = lockingScript[scriptLen - 1 :]; 23 | // action must be issue 24 | require(matchAction == NonFungibleToken.ISSUE); 25 | 26 | PubKey issuer = PubKey(lockingScript[constStart + Util.DataLen : scriptLen - 1]); 27 | // authorize: only the issuer can mint new tokens 28 | require(checkSig(issuerSig, issuer)); 29 | 30 | int currTokenId = unpack(lockingScript[constStart : constStart + Util.DataLen]); 31 | 32 | // increment token ID to mint a new token 33 | bytes outputScript0 = constPart + num2bin(currTokenId + 1, Util.DataLen) + issuer + NonFungibleToken.ISSUE; 34 | bytes output0 = Util.buildOutput(outputScript0, satoshiAmount0); 35 | 36 | // transfer previous token to another receiver 37 | bytes outputScript1 = constPart + num2bin(currTokenId, Util.DataLen) + receiver + NonFungibleToken.TRANSFER; 38 | bytes output1 = Util.buildOutput(outputScript1, satoshiAmount1); 39 | 40 | // check outputs 41 | Sha256 hashOutputs = hash256(output0 + output1); 42 | require(hashOutputs == Util.hashOutputs(txPreimage)); 43 | } 44 | 45 | public function transfer(Sig senderSig, PubKey receiver, int satoshiAmount, SigHashPreimage txPreimage) { 46 | require(Tx.checkPreimage(txPreimage)); 47 | 48 | // read previous locking script: codePart + OP_RETURN + tokenID + ownerPublicKey + matchAction 49 | bytes lockingScript = Util.scriptCode(txPreimage); 50 | int scriptLen = len(lockingScript); 51 | 52 | // constant part of locking script: upto tokenID 53 | int constStart = scriptLen - Util.PubKeyLen - 1; 54 | bytes constPart = lockingScript[: constStart]; 55 | 56 | bytes matchAction = lockingScript[scriptLen - 1 :]; 57 | // action must be transfer 58 | require(matchAction == NonFungibleToken.TRANSFER); 59 | 60 | PubKey sender = PubKey(lockingScript[constStart : scriptLen - 1]); 61 | // authorize 62 | require(checkSig(senderSig, sender)); 63 | 64 | // transfer 65 | bytes outputScript = constPart + receiver + NonFungibleToken.TRANSFER; 66 | 67 | bytes output = Util.buildOutput(outputScript, satoshiAmount); 68 | require(hash256(output) == Util.hashOutputs(txPreimage)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/test_contract_token.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import PubKey, Sig, SigHashPreimage 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte 9 | 10 | 11 | key_priv_0 = PrivateKey.from_arbitrary_bytes(b'test123') 12 | key_pub_0 = key_priv_0.public_key 13 | key_priv_1 = PrivateKey.from_arbitrary_bytes(b'123test') 14 | key_pub_1 = key_priv_1.public_key 15 | 16 | contract = './test/res/token.scrypt' 17 | 18 | compiler_result = scryptlib.utils.compile_contract(contract) 19 | desc = compiler_result.to_desc() 20 | 21 | Token = scryptlib.contract.build_contract_class(desc) 22 | type_classes = scryptlib.contract.build_type_classes(desc) 23 | Account = type_classes['Account'] 24 | 25 | accounts = [Account({ 26 | 'pubKey': PubKey(key_pub_0), 27 | 'balance': 100 28 | }), 29 | Account({ 30 | 'pubKey': PubKey(key_pub_1), 31 | 'balance': 0 32 | })] 33 | token = Token(accounts) 34 | 35 | # Create context and set prev locking script. 36 | context = scryptlib.utils.create_dummy_input_context() 37 | context.utxo.script_pubkey = token.locking_script 38 | 39 | # Create new locking script. 40 | new_accounts = [Account({ 41 | 'pubKey': PubKey(key_pub_0), 42 | 'balance': 60 43 | }), 44 | Account({ 45 | 'pubKey': PubKey(key_pub_1), 46 | 'balance': 40 47 | })] 48 | 49 | new_locking_script = token.get_state_script({'accounts': new_accounts}) 50 | tx_out = TxOutput(value=222222, script_pubkey=new_locking_script) 51 | context.tx.outputs.append(tx_out) 52 | 53 | # Create signature. 54 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 55 | input_idx = 0 56 | utxo_satoshis = context.utxo.value 57 | sighash = context.tx.signature_hash(input_idx, utxo_satoshis, token.locking_script, sighash_flag) 58 | sig = key_priv_0.sign(sighash, hasher=None) 59 | sig = sig + pack_byte(sighash_flag) 60 | 61 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 62 | 63 | 64 | def test_verify_correct(): 65 | verify_result = token.transfer(PubKey(key_pub_0), 66 | Sig(sig), 67 | PubKey(key_pub_1), 68 | 40, 69 | SigHashPreimage(preimage), 70 | 222222).verify(context) 71 | assert verify_result == True 72 | 73 | 74 | def test_verify_wrong_val(): 75 | token.first_call = True 76 | 77 | verify_result = token.transfer(PubKey(key_pub_0), 78 | Sig(sig), 79 | PubKey(key_pub_1), 80 | 40, 81 | SigHashPreimage(preimage), 82 | 221222).verify(context) 83 | assert verify_result == False 84 | 85 | 86 | def test_verify_wrong_val_2(): 87 | token.first_call = True 88 | 89 | verify_result = token.transfer(PubKey(key_pub_0), 90 | Sig(sig), 91 | PubKey(key_pub_1), 92 | 43, 93 | SigHashPreimage(preimage), 94 | 222222).verify(context) 95 | assert verify_result == False 96 | 97 | def test_verify_not_first_call(): 98 | token.first_call = False 99 | 100 | with pytest.raises(bitcoinx.errors.NullFailError): 101 | token.transfer(PubKey(key_pub_0), 102 | Sig(sig), 103 | PubKey(key_pub_1), 104 | 40, 105 | SigHashPreimage(preimage), 106 | 222222).verify(context) 107 | -------------------------------------------------------------------------------- /test/res/faucet.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | contract FaucetDeposit { 4 | public function deposit(SigHashPreimage preImage, int depositAmount, Ripemd160 changePKH, int changeAmount) { 5 | require(Tx.checkPreimage(preImage)); 6 | require(depositAmount > 0); 7 | 8 | bytes output0 = this.composeOutput0(preImage, depositAmount); 9 | bytes output1 = this.composeOutput1(changePKH, changeAmount); 10 | Sha256 hashOutputs = hash256(output0 + output1); 11 | require(hashOutputs == Util.hashOutputs(preImage)); 12 | } 13 | 14 | function composeOutput0(SigHashPreimage preImage, int depositAmount) : bytes { 15 | bytes lockingScript = Util.scriptCode(preImage); 16 | int contractTotalAmount = Util.value(preImage) + depositAmount; 17 | return Util.buildOutput(lockingScript, contractTotalAmount); 18 | } 19 | 20 | function composeOutput1(Ripemd160 changePKH, int changeAmount) : bytes { 21 | bytes output1 = b''; 22 | if (changeAmount > 546) { 23 | output1 = Util.buildOutput(Util.buildPublicKeyHashScript(changePKH), changeAmount); 24 | } 25 | return output1; 26 | } 27 | } 28 | 29 | contract FaucetWithdraw { 30 | public function withdraw(SigHashPreimage preImage, Ripemd160 receiverPKH) { 31 | require(Tx.checkPreimage(preImage)); 32 | //nLockTime is valid when nSequence < 0xFFFFFFFF https://wiki.bitcoinsv.io/index.php/NLocktime_and_nSequence 33 | require(Util.nSequence(preImage) < 0xffffffff); 34 | 35 | int withdrawAmount = 2000000; 36 | int fee = 3000; 37 | bytes output0 = this.composeOutput0(preImage, withdrawAmount, fee); 38 | bytes output1 = this.composeOutput1(receiverPKH, withdrawAmount); 39 | Sha256 hashOutputs = hash256(output0 + output1); 40 | require(hashOutputs == Util.hashOutputs(preImage)); 41 | } 42 | 43 | function getPrevMatureTime(bytes lockingScript, int scriptLen) : int { 44 | return unpack(lockingScript[scriptLen - 4 :]); 45 | } 46 | 47 | function getCodePart(bytes lockingScript, int scriptLen) : bytes { 48 | return lockingScript[0 : scriptLen - 4]; 49 | } 50 | 51 | function composeOutput0(SigHashPreimage preImage, int withdrawAmount, int fee) : bytes { 52 | bytes prevLockingScript = Util.scriptCode(preImage); 53 | int scriptLen = len(prevLockingScript); 54 | 55 | int fiveMinutesInSecond = 300; 56 | int newMatureTime = this.getPrevMatureTime(prevLockingScript, scriptLen) + fiveMinutesInSecond; 57 | require(Util.nLocktime(preImage) == newMatureTime); 58 | 59 | bytes codePart = this.getCodePart(prevLockingScript, scriptLen); 60 | bytes script = codePart + pack(newMatureTime); 61 | int amount = Util.value(preImage) - withdrawAmount - fee; 62 | return Util.buildOutput(script, amount); 63 | } 64 | 65 | function composeOutput1(Ripemd160 receiver, int withdrawAmount) : bytes { 66 | bytes script = Util.buildPublicKeyHashScript(receiver); 67 | return Util.buildOutput(script, withdrawAmount); 68 | } 69 | } 70 | 71 | contract Faucet { 72 | public function deposit(SigHashPreimage preImage, int depositAmount, Ripemd160 changePKH, int changeAmount) { 73 | FaucetDeposit depositContract = new FaucetDeposit(); 74 | require(depositContract.deposit(preImage, depositAmount, changePKH, changeAmount)); 75 | } 76 | 77 | public function withdraw(SigHashPreimage preImage, Ripemd160 receiver) { 78 | FaucetWithdraw withdrawContract = new FaucetWithdraw(); 79 | require(withdrawContract.withdraw(preImage, receiver)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/res/rps.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * Two players play Rock Paper Scissors 5 | * 6 | * Game start by player A, put some bsv in the contract, 7 | * then player B follow, put half bsv in the contract, 8 | * then player A finish it. 9 | */ 10 | contract RockPaperScissors { 11 | static const int INIT = 0; 12 | static const int ROCK = 1; 13 | static const int PAPER = 2; 14 | static const int SCISSORS = 3; 15 | 16 | public function follow(SigHashPreimage txPreimage, int action, Ripemd160 playerBpkh, int satoshiAmount) { 17 | SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID; 18 | // this ensures the preimage is for the current tx 19 | require(Util.checkPreimageSigHashType(txPreimage, sigHashType)); 20 | 21 | // valid action 22 | require(action > 0 && action < 4); 23 | 24 | bytes lockingScript = Util.scriptCode(txPreimage); 25 | int scriptLen = len(lockingScript); 26 | 27 | // init action 28 | int initAction = unpack(lockingScript[scriptLen - Util.DataLen :]); 29 | require(initAction == RockPaperScissors.INIT); 30 | 31 | int satoshiInit = Util.value(txPreimage); 32 | bytes codePart = lockingScript[: scriptLen - Util.PubKeyHashLen - Util.DataLen]; 33 | 34 | bytes outputScript0 = codePart + playerBpkh + num2bin(action, Util.DataLen); 35 | bytes output0 = Util.buildOutput(outputScript0, satoshiInit * 3 / 2); 36 | 37 | bytes lockingScript1 = Util.buildPublicKeyHashScript(playerBpkh); 38 | bytes output1 = Util.buildOutput(lockingScript1, satoshiAmount); 39 | 40 | Sha256 hashOutputs = hash256(output0 + output1); 41 | 42 | require(hashOutputs == Util.hashOutputs(txPreimage)); 43 | } 44 | 45 | public function finish(SigHashPreimage txPreimage, int action, Sig sig, PubKey playerA, int satoshiAmountA) { 46 | SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID; 47 | // this ensures the preimage is for the current tx 48 | require(Util.checkPreimageSigHashType(txPreimage, sigHashType)); 49 | 50 | // valid action 51 | require(action > 0 && action < 4); 52 | 53 | int satoshiTotal = Util.value(txPreimage); 54 | bytes lockingScript = Util.scriptCode(txPreimage); 55 | int scriptLen = len(lockingScript); 56 | 57 | int bAction = unpack(lockingScript[scriptLen - Util.DataLen :]); 58 | require(bAction != RockPaperScissors.INIT); 59 | 60 | Ripemd160 playerAdata = Ripemd160(lockingScript[scriptLen - Util.PubKeyHashLen * 2 - Util.DataLen : scriptLen - Util.PubKeyHashLen - Util.DataLen]); 61 | // authorize 62 | require(hash160(num2bin(action, Util.DataLen) + playerA) == playerAdata); 63 | require(checkSig(sig, playerA)); 64 | 65 | int satoshiAmountB = satoshiTotal * 1 / 3; 66 | if (action == (bAction % 3 + 1)) { 67 | // a win 68 | satoshiAmountB = 0; 69 | } 70 | else if (bAction == (action % 3 + 1)) { 71 | // a lose 72 | satoshiAmountB = satoshiTotal * 2 / 3; 73 | } 74 | 75 | Ripemd160 playerApkh = Ripemd160(hash160(playerA)); 76 | bytes lockingScript0 = Util.buildPublicKeyHashScript(playerApkh); 77 | bytes output0 = Util.buildOutput(lockingScript0, satoshiAmountA); 78 | 79 | bytes output1 = b''; 80 | if (satoshiAmountB > 0) { 81 | Ripemd160 playerBpkh = Ripemd160(lockingScript[scriptLen - Util.PubKeyHashLen - Util.DataLen : scriptLen - Util.DataLen]); 82 | bytes lockingScript1 = Util.buildPublicKeyHashScript(playerBpkh); 83 | output1 = Util.buildOutput(lockingScript1, satoshiAmountB); 84 | } 85 | 86 | Sha256 hashOutputs = hash256(output0 + output1); 87 | require(hashOutputs == Util.hashOutputs(txPreimage)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/test_contract_secp256k1.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | import json 3 | 4 | from bitcoinx import PrivateKey, double_sha256, Signature 5 | from scryptlib import ( 6 | build_contract_class, build_type_classes 7 | ) 8 | 9 | 10 | key_priv = PrivateKey.from_arbitrary_bytes(b'test123') 11 | key_pub = key_priv.public_key 12 | 13 | # Point addition 14 | to_add = PrivateKey.from_arbitrary_bytes(b'bla') 15 | point_sum = key_pub.add(to_add._secret) 16 | assert point_sum.to_hex(compressed=True) == '0210665ad464f2b7a382841d7f764044877db2c988240149a2b1c0e330df5c6f26' 17 | 18 | # Point doubling 19 | point_doubled = key_pub.add(key_priv._secret) 20 | assert point_doubled.to_hex(compressed=True) == '02d955f9f2eb090bcda5f0f158d569880dbe307cfac6a982b674116f7cf013b875' 21 | 22 | # Scalar multiplication 23 | scalar = PrivateKey.from_arbitrary_bytes(b'123test123') 24 | point_scaled = key_pub.multiply(scalar._secret) 25 | assert point_scaled.to_hex(compressed=True) == '022253ec8426dbfe9e844166aa630eb2b654eaac6f3d5d0cfdd90d475ccc026c24' 26 | 27 | # Signature verification 28 | msg = 'Hello, World!' 29 | msg_bytes = str.encode(msg, encoding='ASCII') 30 | sig = key_priv.sign(msg_bytes, hasher=double_sha256) 31 | assert key_pub.verify_der_signature(sig, msg_bytes, hasher=double_sha256) 32 | 33 | r = Signature.r_value(sig) 34 | s = Signature.s_value(sig) 35 | 36 | 37 | ############################ 38 | #################### sCrypt 39 | 40 | contract = './test/res/secp256k1.scrypt' 41 | 42 | #compiler_result = compile_contract(contract, debug=False) 43 | #desc = compiler_result.to_desc() 44 | 45 | # Load desc instead: 46 | with gzip.open('./test/res/desc/secp256k1_desc.json.gz', 'r') as f: 47 | desc = json.load(f) 48 | 49 | type_classes = build_type_classes(desc) 50 | Point = type_classes['Point'] 51 | Signature = type_classes['Signature'] 52 | 53 | CheckSig = build_contract_class(desc) 54 | checkSig = CheckSig() 55 | 56 | ax, ay = key_pub.to_point() 57 | bx, by = to_add.public_key.to_point() 58 | sumx, sumy = point_sum.to_point() 59 | dx, dy = point_doubled.to_point() 60 | prodx, prody = point_scaled.to_point() 61 | 62 | # Point addition 63 | def test_add_on_chain_correct(): 64 | assert checkSig.testAdd( 65 | Point({ 'x': ax, 'y': ay}), 66 | Point({ 'x': bx, 'y': by}), 67 | Point({ 'x': sumx, 'y': sumy}), 68 | ).verify() 69 | 70 | # Point doubling 71 | def test_double_on_chain_correct(): 72 | assert checkSig.testDouble( 73 | Point({ 'x': ax, 'y': ay}), 74 | Point({ 'x': dx, 'y': dy}), 75 | ).verify() 76 | 77 | # Point doubling, point at inf 78 | def test_double_inf_on_chain_correct(): 79 | assert checkSig.testDouble( 80 | Point({ 'x': 0, 'y': 0}), 81 | Point({ 'x': 0, 'y': 0}), 82 | ).verify() 83 | 84 | 85 | # Point addition, same point 86 | def test_add_same_p_on_chain_correct(): 87 | assert checkSig.testAdd( 88 | Point({ 'x': ax, 'y': ay}), 89 | Point({ 'x': ax, 'y': ay}), 90 | Point({ 'x': dx, 'y': dy}), 91 | ).verify() 92 | 93 | # Point addition, point at inf 94 | def test_add_inf_on_chain_correct(): 95 | assert checkSig.testAdd( 96 | Point({ 'x': 0, 'y': 0}), 97 | Point({ 'x': bx, 'y': by}), 98 | Point({ 'x': bx, 'y': by}), 99 | ).verify() 100 | assert checkSig.testAdd( 101 | Point({ 'x': ax, 'y': ay}), 102 | Point({ 'x': 0, 'y': 0}), 103 | Point({ 'x': ax, 'y': ay}), 104 | ).verify() 105 | 106 | # Scalar multiplication 107 | def test_mult_on_chain_correct(): 108 | assert checkSig.testMultByScalar( 109 | Point({ 'x': ax, 'y': ay}), 110 | scalar.to_int(), 111 | Point({ 'x': prodx, 'y': prody}), 112 | ).verify() 113 | 114 | # Signature verification 115 | def test_sig_verify_chain_correct(): 116 | assert checkSig.testVerifySig( 117 | msg_bytes, 118 | Signature({ 'r': r, 's': s}), 119 | Point({ 'x': ax, 'y': ay}), 120 | ).verify() 121 | 122 | -------------------------------------------------------------------------------- /test/res/merkleTree.scrypt: -------------------------------------------------------------------------------- 1 | library MerkleTree { 2 | static const int N = 33; 3 | static const int LOOPCOUNT = 20; 4 | 5 | static function calculateMerkleRoot(bytes leaf, bytes merklePath) : bytes { 6 | int merklePathLength = len(merklePath) / N; 7 | bytes merkleValue = leaf; 8 | loop (LOOPCOUNT) : i { 9 | if (i < merklePathLength) { 10 | 11 | int left = unpack(merklePath[i * N + N - 1 : i * N + N]); 12 | if (left) { 13 | merkleValue = sha256(merkleValue + merklePath[i * N : i * N + N - 1]); 14 | } 15 | else { 16 | merkleValue = sha256(merklePath[i * N : i * N + N - 1] + merkleValue); 17 | } 18 | } 19 | } 20 | return merkleValue; 21 | } 22 | 23 | static function verifyLeaf(bytes leaf, bytes merklePath, bytes merkleRoot) : bool { 24 | bytes merkleValue = MerkleTree.calculateMerkleRoot(leaf, merklePath); 25 | return merkleValue == merkleRoot; 26 | } 27 | 28 | static function updateLeaf(bytes oldLeaf, bytes newLeaf, bytes merklePath, bytes oldMerkleRoot) : bytes { 29 | 30 | int merklePathLength = len(merklePath) / N; 31 | bytes oldMerkleValue = oldLeaf; 32 | bytes newMerkleValue = newLeaf; 33 | 34 | loop (LOOPCOUNT) : i { 35 | if (i < merklePathLength) { 36 | 37 | int left = unpack(merklePath[i * N + N - 1 : i * N + N]); 38 | bytes oldNeighbor = merklePath[i * N : i * N + N - 1]; 39 | bytes newNeighbor = oldNeighbor == oldMerkleValue ? newMerkleValue : oldNeighbor; 40 | 41 | if (left) { 42 | oldMerkleValue = sha256(oldMerkleValue + oldNeighbor); 43 | newMerkleValue = sha256(newMerkleValue + newNeighbor); 44 | } 45 | else { 46 | oldMerkleValue = sha256(oldNeighbor + oldMerkleValue); 47 | newMerkleValue = sha256(newNeighbor + newMerkleValue); 48 | } 49 | } 50 | } 51 | 52 | require(oldMerkleValue == oldMerkleRoot); 53 | 54 | return newMerkleValue; 55 | } 56 | 57 | static function addLeaf(bytes lastLeaf, bytes lastMerklePath, bytes oldMerkleRoot, bytes newLeaf) : bytes { 58 | require(MerkleTree.verifyLeaf(lastLeaf, lastMerklePath, oldMerkleRoot)); 59 | 60 | int depth = len(lastMerklePath) / N; 61 | bytes merkleValue = newLeaf; 62 | bytes lastLeafValue = lastLeaf; 63 | bool joined = false; 64 | 65 | loop (LOOPCOUNT) : i { 66 | if (i < depth) { 67 | bytes sibling = lastMerklePath[i * N : i * N + N - 1]; 68 | int left = unpack(lastMerklePath[i * N + N - 1 : i * N + N]); 69 | 70 | if (left) { 71 | if (joined) { 72 | require(sibling == merkleValue); 73 | merkleValue = sha256(merkleValue + sibling); 74 | } 75 | else { 76 | require(sibling == lastLeafValue); 77 | merkleValue = sha256(lastLeafValue + merkleValue); 78 | } 79 | joined = true; 80 | } 81 | else { 82 | if (joined) { 83 | merkleValue = sha256(sibling + merkleValue); 84 | } 85 | else { 86 | merkleValue = sha256(merkleValue + merkleValue); 87 | lastLeafValue = sha256(sibling + lastLeafValue); 88 | } 89 | } 90 | } 91 | } 92 | 93 | if (!joined) { 94 | merkleValue = sha256(oldMerkleRoot + merkleValue); 95 | } 96 | 97 | return merkleValue; 98 | } 99 | 100 | /** 101 | * Makes sure that the new leaf is added at the same depth. 102 | */ 103 | static function addLeafSafe(bytes lastEntry, bytes lastMerklePath, bytes oldMerkleRoot, bytes newLeaf) : bytes { 104 | bytes lastLeaf = sha256(lastEntry); 105 | return MerkleTree.addLeaf(lastLeaf, lastMerklePath, oldMerkleRoot, newLeaf); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/res/ecVerify.scrypt: -------------------------------------------------------------------------------- 1 | 2 | struct Point { 3 | int x; 4 | int y; 5 | } 6 | 7 | // auxiliary data to verify point multiplication 8 | struct PointMulAux { 9 | Point[ECVerify.N] pMultiples; 10 | Point[ECVerify.N] qs; 11 | int[ECVerify.N1] lambdas1; 12 | int[ECVerify.N1] lambdas2; 13 | } 14 | 15 | // elliptic curve Secp256k1 16 | library ECVerify { 17 | // prime modulus 18 | static const int P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f; 19 | // group order 20 | static const int n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; 21 | 22 | // in product , should use N = 256, N1 = 255 23 | // Here we use a smaller number to provide compilation speed 24 | static const int N = 16; 25 | static const int N1 = 15; // N - 1 26 | 27 | static const Point ZERO = { 0, 0 }; 28 | static const Point G = { 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 }; 29 | 30 | static function isSumHelper(Point p1, Point p2, int lambda, Point p) : bool { 31 | // check lambda is indeed gradient 32 | bool lambdaOK = (p1 == p2) ? 33 | (2 * lambda * p1.y - 3 * p1.x * p1.x) % P == 0 : 34 | (lambda * (p2.x - p1.x) - (p2.y - p1.y)) % P == 0; 35 | // also check p = p1 + p2 36 | return lambdaOK && (lambda * lambda - p1.x - p2.x - p.x) % P == 0 && 37 | (lambda * (p1.x - p.x) - p1.y - p.y) % P == 0; 38 | } 39 | 40 | // return true if lambda is the gradient of the line between p1 and p2 41 | // and p = p1 + p2 42 | static function isSum(Point p1, Point p2, int lambda, Point p) : bool { 43 | // special handling of point ZERO 44 | // if no point equals to ZERO, call isSumHelper() 45 | return p1 == ZERO ? p2 == p : (p2 == ZERO ? p1 == p : (p1.x == p2.x && (p1.y + p2.y) % P == 0) ? p == ZERO : isSumHelper(p1, p2, lambda, p)); 46 | } 47 | 48 | // same as isSum(), but using type PubKey 49 | static function isPubKeySum(PubKey p1, PubKey p2, int lambda, PubKey p) : bool { 50 | return isSum(pubKey2Point(p1), pubKey2Point(p2), lambda, pubKey2Point(p)); 51 | } 52 | 53 | // return true iff p * x == r 54 | static function isMul(Point p, int x, Point r, PointMulAux pma) : bool { 55 | 56 | // validate pMultiples = [p, 2p, 4p, 8p, ...] 57 | loop (N) : i { 58 | require(i == 0 ? pma.pMultiples[i] == p : isSum(pma.pMultiples[i - 1], pma.pMultiples[i - 1], pma.lambdas1[i - 1], pma.pMultiples[i])); 59 | } 60 | 61 | // // x * p = x0 * p + x1 *(2p) + x2 * (4p) + x3 * (8p) + ... 62 | // // xi is the i-th bit of x 63 | Point P0 = ZERO; 64 | loop (N) : i { 65 | Point P = x % 2 ? pma.pMultiples[i] : ZERO; 66 | 67 | // right shift by 1 68 | x /= 2; 69 | 70 | if (i == 0) { 71 | P0 = P; 72 | } 73 | else if (i == 1) { 74 | // first 75 | require(isSum(P0, P, pma.lambdas2[i - 1], pma.qs[i - 1])); 76 | } 77 | else { 78 | // rest 79 | require(isSum(pma.qs[i - 1], P, pma.lambdas2[i - 1], i < N1 ? pma.qs[i] : r)); 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | // convert a public key to a point, assuming it's uncompressed 87 | static function pubKey2Point(PubKey pubKey) : Point { 88 | require(pubKey[: 1] == b'04'); 89 | //convert signed little endian to unsigned big endian 90 | int x = Utils.fromLEUnsigned(reverseBytes(pubKey[1 : 33], 32)); 91 | int y = Utils.fromLEUnsigned(reverseBytes(pubKey[33 : 65], 32)); 92 | return {x, y}; 93 | } 94 | 95 | // convert a point to a uncompressed public key 96 | static function point2PubKey(Point point) : PubKey { 97 | //serializer point.x, point.y to unsigned big endian number 98 | return PubKey(b'04' + toBEUnsigned(point.x, 32) + toBEUnsigned(point.y, 32)); 99 | } 100 | 101 | // convert signed integer `n` to unsigned integer of `l` bytes, in big endian 102 | static function toBEUnsigned(int n, static const int l) : bytes { 103 | bytes m = Utils.toLEUnsigned(n, l); 104 | return reverseBytes(m,l); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/res/merkleToken.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | import "./merkleTree.scrypt"; 3 | 4 | contract merkleToken { 5 | int price; 6 | 7 | public function buyMore(SigHashPreimage txPreimage, int amount, Ripemd160 changePKH, Ripemd160 payoutPKH, int changeSats, int prevBalance, bytes merklePath) { 8 | 9 | SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID; 10 | require(Util.checkPreimageSigHashType(txPreimage, sigHashType)); 11 | 12 | require(amount > 0); 13 | 14 | bytes scriptCode = Util.scriptCode(txPreimage); 15 | int scriptLen = len(scriptCode); 16 | 17 | bytes balanceTableRoot = scriptCode[scriptLen - 32 :]; 18 | 19 | bytes oldLeaf = sha256(payoutPKH + num2bin(prevBalance, 1)); 20 | bytes newLeaf = sha256(payoutPKH + num2bin(prevBalance + amount, 1)); 21 | bytes newBalanceTableRoot = MerkleTree.updateLeaf(oldLeaf, newLeaf, merklePath, balanceTableRoot); 22 | 23 | bytes newScriptCode = scriptCode[: scriptLen - 32] + newBalanceTableRoot; 24 | 25 | int cost = amount * this.price; 26 | 27 | int newBalance = Util.value(txPreimage) + cost; 28 | bytes marketOutput = Util.buildOutput(newScriptCode, newBalance); 29 | 30 | // Expect the additional CHANGE output 31 | bytes changeScript = Util.buildPublicKeyHashScript(changePKH); 32 | bytes changeOutput = Util.buildOutput(changeScript, changeSats); 33 | 34 | Sha256 hashOutputs = hash256(marketOutput + changeOutput); 35 | 36 | require(hashOutputs == Util.hashOutputs(txPreimage)); 37 | } 38 | 39 | public function buy(SigHashPreimage txPreimage, int amount, Ripemd160 changePKH, Ripemd160 payoutPKH, int changeSats, bytes lastEntry, bytes lastMerklePath) { 40 | 41 | SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID; 42 | require(Util.checkPreimageSigHashType(txPreimage, sigHashType)); 43 | 44 | require(amount > 0); 45 | 46 | bytes scriptCode = Util.scriptCode(txPreimage); 47 | int scriptLen = len(scriptCode); 48 | 49 | bytes balanceTableRoot = scriptCode[scriptLen - 32 :]; 50 | 51 | // Using the entry makes sure that new Leaf are added at the same depth 52 | bytes newLeaf = sha256(payoutPKH + num2bin(amount, 1)); 53 | bytes newBalanceTableRoot = MerkleTree.addLeafSafe(lastEntry, lastMerklePath, balanceTableRoot, newLeaf); 54 | 55 | bytes newScriptCode = scriptCode[: scriptLen - 32] + newBalanceTableRoot; 56 | 57 | int cost = amount * this.price; 58 | 59 | int newBalance = Util.value(txPreimage) + cost; 60 | bytes marketOutput = Util.buildOutput(newScriptCode, newBalance); 61 | 62 | // Expect the additional CHANGE output 63 | bytes changeScript = Util.buildPublicKeyHashScript(changePKH); 64 | bytes changeOutput = Util.buildOutput(changeScript, changeSats); 65 | 66 | Sha256 hashOutputs = hash256(marketOutput + changeOutput); 67 | 68 | require(hashOutputs == Util.hashOutputs(txPreimage)); 69 | 70 | } 71 | 72 | public function sell(SigHashPreimage txPreimage, int amount, PubKey pubKey, Sig sig, bytes merklePath, int oldBalance, int payoutSats) { 73 | 74 | require(Tx.checkPreimage(txPreimage)); 75 | require(amount > 0); 76 | 77 | bytes scriptCode = Util.scriptCode(txPreimage); 78 | int scriptLen = len(scriptCode); 79 | 80 | bytes balanceTableRoot = scriptCode[scriptLen - 32 :]; 81 | 82 | Ripemd160 address = hash160(pubKey); 83 | require(checkSig(sig, pubKey)); 84 | 85 | int newBalance = oldBalance - amount; 86 | require(newBalance >= 0); 87 | 88 | bytes oldEntry = address + num2bin(oldBalance, 1); 89 | bytes newEntry = address + num2bin(newBalance, 1); 90 | 91 | bytes newBalanceTableRoot = MerkleTree.updateLeaf(sha256(oldEntry), sha256(newEntry), merklePath, balanceTableRoot); 92 | 93 | bytes newScriptCode = scriptCode[: scriptLen - 32] + newBalanceTableRoot; 94 | int credit = amount * this.price; 95 | int newMarketBalance = Util.value(txPreimage) - credit; 96 | 97 | bytes marketOutput = Util.buildOutput(newScriptCode, newMarketBalance); 98 | 99 | bytes payoutScript = Util.buildPublicKeyHashScript(address); 100 | bytes payoutOutput = Util.buildOutput(payoutScript, payoutSats); 101 | 102 | Sha256 hashOutputs = hash256(marketOutput + payoutOutput); 103 | 104 | require(hashOutputs == Util.hashOutputs(txPreimage)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/test_contract_faucet.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import SigHashPreimage, Int, Ripemd160, Sha256, PubKey, Sig, Bytes 6 | 7 | import bitcoinx 8 | from bitcoinx import SigHash, TxOutput, Script, PrivateKey, P2PKH_Address, Bitcoin, pack_byte 9 | 10 | 11 | contract = './test/res/faucet.scrypt' 12 | 13 | compiler_result = scryptlib.utils.compile_contract(contract) 14 | desc = compiler_result.to_desc() 15 | 16 | key_priv = PrivateKey.from_arbitrary_bytes(b'test0') 17 | key_pub = key_priv.public_key 18 | pkh = key_pub.hash160() 19 | 20 | Faucet = scryptlib.contract.build_contract_class(desc) 21 | faucet = Faucet() 22 | 23 | 24 | def test_verify_without_change(): 25 | deposit_sats = 100000 26 | input_sats = 100000 27 | output_sats = deposit_sats + input_sats 28 | 29 | faucet.set_data_part(scryptlib.utils.get_push_int(1602553516)) 30 | 31 | context = scryptlib.utils.create_dummy_input_context() 32 | context.utxo.script_pubkey = faucet.locking_script 33 | context.utxo.value = input_sats 34 | 35 | tx_out = TxOutput(value=output_sats, script_pubkey=faucet.locking_script) 36 | context.tx.outputs.append(tx_out) 37 | 38 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 39 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 40 | 41 | verify_result = faucet.deposit(SigHashPreimage(preimage), deposit_sats, 42 | Ripemd160(pkh), 0).verify(context) 43 | assert verify_result == True 44 | 45 | with pytest.raises(bitcoinx.VerifyFailed): 46 | faucet.deposit(SigHashPreimage(preimage), deposit_sats - 1, 47 | Ripemd160(pkh), 0).verify(context) 48 | 49 | 50 | 51 | def test_verify_with_change(): 52 | deposit_sats = 100000 53 | input_sats = 100000 54 | output_sats = deposit_sats + input_sats 55 | change_sats = 547 56 | 57 | faucet.set_data_part(scryptlib.utils.get_push_int(1602553516)) 58 | 59 | context = scryptlib.utils.create_dummy_input_context() 60 | context.utxo.script_pubkey = faucet.locking_script 61 | context.utxo.value = input_sats 62 | 63 | tx_out_0 = TxOutput(value=output_sats, script_pubkey=faucet.locking_script) 64 | context.tx.outputs.append(tx_out_0) 65 | 66 | tx_out_1 = TxOutput(value=change_sats, script_pubkey=P2PKH_Address(pkh, Bitcoin).to_script()) 67 | context.tx.outputs.append(tx_out_1) 68 | 69 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 70 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 71 | 72 | verify_result = faucet.deposit(SigHashPreimage(preimage), deposit_sats, 73 | Ripemd160(pkh), change_sats).verify(context) 74 | assert verify_result == True 75 | 76 | with pytest.raises(bitcoinx.VerifyFailed): 77 | faucet.deposit(SigHashPreimage(preimage), deposit_sats, 78 | Ripemd160(pkh), change_sats + 1).verify(context) 79 | 80 | 81 | def test_verify_withdraw(): 82 | withdraw_sats = 2000000 83 | fee = 3000 84 | input_sats = 10000000 85 | output_sats = input_sats - withdraw_sats - fee 86 | mature_time = 1627122529 87 | 88 | faucet.set_data_part(scryptlib.utils.get_push_int(mature_time)) 89 | 90 | context = scryptlib.utils.create_dummy_input_context() 91 | context.utxo.script_pubkey = faucet.locking_script 92 | context.utxo.value = input_sats 93 | 94 | new_locking_script = faucet.code_part << Script(scryptlib.utils.get_push_int(mature_time + 300)) 95 | tx_out_0 = TxOutput(value=output_sats, script_pubkey=new_locking_script) 96 | context.tx.outputs.append(tx_out_0) 97 | 98 | tx_out_1 = TxOutput(value=withdraw_sats, script_pubkey=P2PKH_Address(pkh, Bitcoin).to_script()) 99 | context.tx.outputs.append(tx_out_1) 100 | 101 | context.tx.inputs[0].sequence = 0xfffffffe 102 | context.tx.locktime = mature_time + 300 103 | 104 | preimage = scryptlib.utils.get_preimage_from_input_context(context) 105 | 106 | verify_result = faucet.withdraw(SigHashPreimage(preimage), Ripemd160(pkh)).verify(context) 107 | assert verify_result == True 108 | 109 | # Wrong mature time 110 | context.tx.outputs = [] 111 | 112 | new_locking_script = faucet.code_part << Script(scryptlib.utils.get_push_int(mature_time + 299)) 113 | tx_out_0 = TxOutput(value=output_sats, script_pubkey=new_locking_script) 114 | context.tx.outputs.append(tx_out_0) 115 | 116 | tx_out_1 = TxOutput(value=withdraw_sats, script_pubkey=P2PKH_Address(pkh, Bitcoin).to_script()) 117 | context.tx.outputs.append(tx_out_1) 118 | 119 | context.tx.locktime = mature_time + 299 120 | 121 | preimage = scryptlib.utils.get_preimage_from_input_context(context) 122 | 123 | with pytest.raises(bitcoinx.VerifyFailed): 124 | faucet.withdraw(SigHashPreimage(preimage), Ripemd160(pkh)).verify(context) 125 | 126 | 127 | -------------------------------------------------------------------------------- /test/test_contract_rps.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage, Ripemd160, Bytes 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script, hash160, P2PKH_Address, Bitcoin 9 | 10 | 11 | key_priv_A = PrivateKey.from_arbitrary_bytes(b'test123') 12 | key_pub_A = key_priv_A.public_key 13 | pkh_A = key_pub_A.hash160() 14 | key_priv_B = PrivateKey.from_arbitrary_bytes(b'test123') 15 | key_pub_B = key_priv_B.public_key 16 | pkh_B = key_pub_B.hash160() 17 | 18 | player_A_data = hash160(b'\x01' + key_pub_A.to_bytes()) 19 | 20 | sighash_flag = SigHash(SigHash.ANYONE_CAN_PAY | SigHash.ALL | SigHash.FORKID) 21 | 22 | pub_key_hashlen = 20 23 | 24 | action_INIT = 0 25 | action_ROCK = 1 26 | action_PAPER = 2 27 | action_SCISSORS = 3 28 | 29 | contract = './test/res/rps.scrypt' 30 | 31 | compiler_result = scryptlib.utils.compile_contract(contract) 32 | desc = compiler_result.to_desc() 33 | 34 | RPS = scryptlib.contract.build_contract_class(desc) 35 | rps = RPS() 36 | 37 | 38 | def test_verify_player_B_follow(): 39 | def test_follow(pkh_B, action, init_sats, input_sats, out_sats, change_sats): 40 | rps.set_data_part(player_A_data + b'\x00' * pub_key_hashlen + scryptlib.utils.get_push_int(action_INIT)[1:]) 41 | 42 | context = scryptlib.utils.create_dummy_input_context() 43 | context.utxo.script_pubkey = rps.locking_script 44 | context.utxo.value = init_sats 45 | 46 | new_data_part = player_A_data + pkh_B + scryptlib.utils.get_push_int(action)[1:] 47 | new_locking_script = Script(rps.code_part.to_bytes() + new_data_part) 48 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 49 | context.tx.outputs.append(tx_out) 50 | 51 | change_out = TxOutput(change_sats, P2PKH_Address(pkh_B, Bitcoin).to_script()) 52 | context.tx.outputs.append(change_out) 53 | 54 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 55 | 56 | return rps.follow(SigHashPreimage(preimage), action, Ripemd160(pkh_B), change_sats).verify(context) 57 | 58 | init_sats = 100000 59 | input_sats = 60000 60 | out_sats = 150000 61 | change_sats = 10000 62 | 63 | verify_result = test_follow(pkh_B, action_PAPER, init_sats, input_sats, out_sats, change_sats) 64 | assert verify_result == True 65 | 66 | 67 | def test_verify_player_A_finish(): 68 | def test_finish(key_priv, pkh_B, action_A, action_B, total_sats, input_sats, out_sats, change_sats): 69 | rps.set_data_part(player_A_data + pkh_B + scryptlib.utils.get_push_int(action_B)[1:]) 70 | 71 | context = scryptlib.utils.create_dummy_input_context() 72 | context.utxo.script_pubkey = rps.locking_script 73 | context.utxo.value = total_sats 74 | 75 | change_out = TxOutput(change_sats, P2PKH_Address(pkh_A, Bitcoin).to_script()) 76 | context.tx.outputs.append(change_out) 77 | 78 | if out_sats > 0: 79 | pay_out = TxOutput(out_sats, P2PKH_Address(pkh_B, Bitcoin).to_script()) 80 | context.tx.outputs.append(pay_out) 81 | 82 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 83 | 84 | input_idx = 0 85 | utxo_satoshis = context.utxo.value 86 | sighash = context.tx.signature_hash(input_idx, utxo_satoshis, rps.locking_script, sighash_flag) 87 | sig = key_priv.sign(sighash, hasher=None) 88 | sig = sig + pack_byte(sighash_flag) 89 | 90 | return rps.finish(SigHashPreimage(preimage), action_A, 91 | Sig(sig), PubKey(key_pub_A), change_sats).verify(context) 92 | 93 | total_sats = 150000 94 | input_sats = 10000 95 | out_sats = 100000 96 | change_sats = 60000 97 | 98 | verify_result = test_finish(key_priv_A, pkh_B, action_ROCK, action_PAPER, total_sats, 99 | input_sats, out_sats, change_sats) 100 | assert verify_result == True 101 | 102 | with pytest.raises(bitcoinx.VerifyFailed): 103 | test_finish(key_priv_A, pkh_B, action_PAPER, action_PAPER, total_sats, 104 | input_sats, out_sats, change_sats) 105 | 106 | total_sats = 150000 107 | input_sats = 10000 108 | out_sats = 0 109 | change_sats = 160000 110 | 111 | verify_result = test_finish(key_priv_A, pkh_B, action_ROCK, action_SCISSORS, total_sats, 112 | input_sats, out_sats, change_sats) 113 | assert verify_result == True 114 | 115 | total_sats = 150000 116 | input_sats = 10000 117 | out_sats = 50000 118 | change_sats = 110000 119 | 120 | verify_result = test_finish(key_priv_A, pkh_B, action_ROCK, action_ROCK, total_sats, 121 | input_sats, out_sats, change_sats) 122 | assert verify_result == True 123 | -------------------------------------------------------------------------------- /test/test_types.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from scryptlib.types import * 4 | 5 | 6 | def test_type_bytes(): 7 | b = Bytes('01') 8 | assert(b.hex == '0101') 9 | 10 | b = Bytes(b'\x01') 11 | assert(b.hex == '0101') 12 | 13 | # OP_PUSHDATA1 14 | b = Bytes('ff' * 100) 15 | assert(b.hex == '4c64' + 'ff' * 100) 16 | b = Bytes('ff' * 255) 17 | assert(b.hex == '4cff' + 'ff' * 255) 18 | 19 | # OP_PUSHDATA2 20 | b = Bytes('ff' * 256) 21 | assert(b.hex == '4d0001' + 'ff' * 256) 22 | b = Bytes('ff' * 65535) 23 | assert(b.hex == '4dffff' + 'ff' * 65535) 24 | 25 | # OP_PUSHDATA4 26 | b = Bytes('ff' * 65536) 27 | assert(b.hex == '4e00000100' + 'ff' * 65536) 28 | 29 | 30 | def test_type_int(): 31 | x = Int(73219837192873198232871937891273981279837198793818) 32 | assert(x.hex == '155abc0013a5e275d529dc04d7e2320ae0a60d5b1932') 33 | 34 | x = Int(-73219837192873198232871937891273981279837198793818) 35 | assert(x.hex == '155abc0013a5e275d529dc04d7e2320ae0a60d5b19b2') 36 | 37 | 38 | def test_type_privkey(): 39 | # Positive 40 | x = PrivKey(bytes.fromhex('7ED697BCE5AEF3F7B09CBD6BBB8EBACF0C53D8B80DD90BACF8644C11648E8784')) 41 | assert(x.hex == '2084878e64114c64f8ac0bd90db8d8530ccfba8ebb6bbd9cb0f7f3aee5bc97d67e') 42 | 43 | x = PrivKey('7ED697BCE5AEF3F7B09CBD6BBB8EBACF0C53D8B80DD90BACF8644C11648E8784') 44 | assert(x.hex == '2084878e64114c64f8ac0bd90db8d8530ccfba8ebb6bbd9cb0f7f3aee5bc97d67e') 45 | 46 | # Negative 47 | x = PrivKey(70024952860251874614749626492917994704208775384514195732065700789540272030212) 48 | assert(x.hex == '2104421d3fb78c05aba0d68817fce03e2b0cf7d058f74705a7ec76288202b8d09a00') 49 | 50 | x = PrivKey(0xc34039e780c90ec8517a556b379954076b04c792035407802f3e65e61c1cd3c5) 51 | assert(x.hex == '21c5d31c1ce6653e2f8007540392c7046b075499376b557a51c80ec980e73940c300') 52 | 53 | 54 | def test_type_hashedmap(): 55 | hm = HashedMap(Int, Int) 56 | hm.set(Int(3), Int(1)) 57 | assert(hm.hex == '084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c54bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a') 58 | hm.set(Int(5), Int(6)) 59 | assert(hm.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db67586e98fad27da0b9968bc039a1ef34c939b9b8e523a8bef89d478608c5ecf6084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c54bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a') 60 | hm.set(0, 11) 61 | assert(hm.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db67586e98fad27da0b9968bc039a1ef34c939b9b8e523a8bef89d478608c5ecf6084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c54bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459ae3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855e7cf46a078fed4fafd0b5e3aff144802b853f8ae459a4f0c14add3314b7cc3a6') 62 | hm.set(Int(1), Int(5)) 63 | assert(hm.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db67586e98fad27da0b9968bc039a1ef34c939b9b8e523a8bef89d478608c5ecf6084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c54bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459ae77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743dbe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855e7cf46a078fed4fafd0b5e3aff144802b853f8ae459a4f0c14add3314b7cc3a6') 64 | 65 | hm.delete(Int(1)) 66 | hm.delete(Int(0)) 67 | assert(hm.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db67586e98fad27da0b9968bc039a1ef34c939b9b8e523a8bef89d478608c5ecf6084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c54bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a') 68 | 69 | hm = HashedMap(Bytes, Int) 70 | 71 | with pytest.raises(AssertionError): 72 | hm.set(Int(0), Int(1)) 73 | 74 | hm.set(Bytes('1234'), Int(11)) 75 | 76 | 77 | def test_type_hashedset(): 78 | hs = HashedSet(Int) 79 | hs.add(3) 80 | assert(hs.hex == '084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5') 81 | hs.add(Int(5)) 82 | assert(hs.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5') 83 | hs.add(0) 84 | assert(hs.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') 85 | hs.add(Int(1)) 86 | assert(hs.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c54bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459ae3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') 87 | 88 | hs.delete(Int(1)) 89 | hs.delete(Int(0)) 90 | assert(hs.hex == 'e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5') 91 | 92 | 93 | -------------------------------------------------------------------------------- /test/res/tokenUtxo.scrypt: -------------------------------------------------------------------------------- 1 | import "util.scrypt"; 2 | 3 | /** 4 | * A token protocol based on UTXO model 5 | */ 6 | contract Token { 7 | public function split(Sig senderSig, PubKey receiver0, int tokenAmount0, int satoshiAmount0, PubKey receiver1, int tokenAmount1, int satoshiAmount1, SigHashPreimage txPreimage) { 8 | require(tokenAmount0 > 0); 9 | // 1 to 1 transfer when tokenAmount1 is 0 10 | require(tokenAmount1 >= 0); 11 | 12 | // this ensures the preimage is for the current tx 13 | require(Tx.checkPreimage(txPreimage)); 14 | 15 | // read previous locking script 16 | // locking script = codePart + OP_RETURN + senderPublicKey + balance0 + balance1 17 | bytes lockingScript = Util.scriptCode(txPreimage); 18 | int scriptLen = len(lockingScript); 19 | 20 | int amountStart = scriptLen - Util.DataLen * 2; 21 | 22 | PubKey sender = PubKey(lockingScript[amountStart - Util.PubKeyLen : amountStart]); 23 | // authorize 24 | require(checkSig(senderSig, sender)); 25 | 26 | int balance0 = unpack(lockingScript[amountStart : amountStart + Util.DataLen]); 27 | int balance1 = unpack(lockingScript[amountStart + Util.DataLen :]); 28 | 29 | // split 30 | require(balance0 + balance1 == tokenAmount0 + tokenAmount1); 31 | 32 | // persist contract code part, including op_return itself 33 | bytes codePart = lockingScript[: amountStart - Util.PubKeyLen]; 34 | 35 | // setting first balance as 0 is just a convention, not a requirement 36 | bytes outputScript0 = codePart + receiver0 + num2bin(0, Util.DataLen) + num2bin(tokenAmount0, Util.DataLen); 37 | bytes output0 = Util.buildOutput(outputScript0, satoshiAmount0); 38 | bytes outputScript1 = codePart + receiver1 + num2bin(0, Util.DataLen) + num2bin(tokenAmount1, Util.DataLen); 39 | bytes output1 = (tokenAmount1 > 0) ? Util.buildOutput(outputScript1, satoshiAmount1) : b''; 40 | Sha256 hashOutputs = hash256(output0 + output1); 41 | 42 | require(hashOutputs == Util.hashOutputs(txPreimage)); 43 | } 44 | 45 | public function merge(Sig senderSig, PubKey receiver, bytes prevouts, int otherTokenAmount, int satoshiAmount, SigHashPreimage txPreimage) { 46 | require(otherTokenAmount >= 0); 47 | 48 | // this ensures the preimage is for the current tx 49 | require(Tx.checkPreimage(txPreimage)); 50 | 51 | // this ensures prevouts is the preimage of hashPrevouts 52 | require(hash256(prevouts) == Util.hashPrevouts(txPreimage)); 53 | // each outpoint: 32 byte txid + 4 byte index 54 | int outpointLen = 36; 55 | // ensure only two inputs are present 56 | require(len(prevouts) == 2 * outpointLen); 57 | 58 | // read previous locking script 59 | bytes lockingScript = Util.scriptCode(txPreimage); 60 | int scriptLen = len(lockingScript); 61 | 62 | int amountStart = scriptLen - Util.DataLen * 2; 63 | 64 | PubKey sender = PubKey(lockingScript[amountStart - Util.PubKeyLen : amountStart]); 65 | // authorize 66 | require(checkSig(senderSig, sender)); 67 | 68 | int balance0 = unpack(lockingScript[amountStart : amountStart + Util.DataLen]); 69 | int balance1 = unpack(lockingScript[amountStart + Util.DataLen :]); 70 | 71 | // persist contract code part, including op_return itself 72 | bytes codePart = lockingScript[: amountStart - Util.PubKeyLen]; 73 | 74 | bytes amountPart = (Util.outpoint(txPreimage) == prevouts[: outpointLen]) ? 75 | // input 0 76 | num2bin(balance0 + balance1, Util.DataLen) + num2bin(otherTokenAmount, Util.DataLen) : 77 | // input 1 78 | num2bin(otherTokenAmount, Util.DataLen) + num2bin(balance0 + balance1, Util.DataLen); 79 | // merge 80 | bytes outputScript = codePart + receiver + amountPart; 81 | bytes output = Util.buildOutput(outputScript, satoshiAmount); 82 | require(hash256(output) == Util.hashOutputs(txPreimage)); 83 | } 84 | 85 | // burn a token back to normal bitcoins 86 | public function burn(Sig senderSig, Ripemd160 receiverPkh, int satoshiAmount, SigHashPreimage txPreimage) { 87 | // this ensures the preimage is for the current tx 88 | require(Tx.checkPreimage(txPreimage)); 89 | 90 | // read previous locking script 91 | // locking script = codePart + OP_RETURN + senderPublicKey + balance0 + balance1 92 | bytes lockingScript = Util.scriptCode(txPreimage); 93 | int scriptLen = len(lockingScript); 94 | 95 | int amountStart = scriptLen - Util.DataLen * 2; 96 | 97 | PubKey sender = PubKey(lockingScript[amountStart - Util.PubKeyLen : amountStart]); 98 | // authorize 99 | require(checkSig(senderSig, sender)); 100 | 101 | // send to a P2PKH script 102 | bytes lockingScript_ = Util.buildPublicKeyHashScript(receiverPkh); 103 | bytes output = Util.buildOutput(lockingScript_, satoshiAmount); 104 | Sha256 hashOutputs = hash256(output); 105 | require(hashOutputs == Util.hashOutputs(txPreimage)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/test_contract_non_fungible_token.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Int, PubKey, Sig, SigHashPreimage 6 | 7 | import bitcoinx 8 | from bitcoinx import PrivateKey, TxOutput, SigHash, pack_byte, Script 9 | 10 | 11 | key_priv_0 = PrivateKey.from_arbitrary_bytes(b'test123') 12 | key_pub_0 = key_priv_0.public_key 13 | key_priv_1 = PrivateKey.from_arbitrary_bytes(b'123test') 14 | key_pub_1 = key_priv_1.public_key 15 | 16 | curr_token_id = 42 17 | issuer = key_pub_0 18 | sender = key_pub_0 19 | 20 | action_issue = b'\x00' 21 | action_transfer = b'\x01' 22 | 23 | input_sats = 100000 24 | out_sats = 22222 25 | 26 | contract = './test/res/nonFungibleToken.scrypt' 27 | 28 | compiler_result = scryptlib.utils.compile_contract(contract) 29 | desc = compiler_result.to_desc() 30 | 31 | Token = scryptlib.contract.build_contract_class(desc) 32 | token = Token() 33 | 34 | 35 | def test_verify_token_issue(): 36 | tok_id_data = scryptlib.utils.get_push_int(curr_token_id)[1:] 37 | issuer_data = scryptlib.utils.get_push_item(issuer.to_bytes())[1:] 38 | 39 | token.set_data_part(scryptlib.utils.get_push_item(tok_id_data + issuer_data + action_issue)) 40 | 41 | 42 | def test_issue(priv_key, receiver, new_issuer=issuer, next_tok_id=curr_token_id + 1, 43 | issued_tok_id=curr_token_id): 44 | context = scryptlib.utils.create_dummy_input_context() 45 | context.utxo.script_pubkey = token.locking_script 46 | context.utxo.value = input_sats 47 | 48 | new_data_part = b'\x23' + scryptlib.utils.get_push_int(next_tok_id)[1:] + \ 49 | new_issuer.to_bytes() + action_issue 50 | new_locking_script = Script(token.code_part.to_bytes() + new_data_part) 51 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 52 | context.tx.outputs.append(tx_out) 53 | 54 | new_data_part = b'\x23' + scryptlib.utils.get_push_int(issued_tok_id)[1:] + \ 55 | receiver.to_bytes() + action_transfer 56 | new_locking_script = Script(token.code_part.to_bytes() + new_data_part) 57 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 58 | context.tx.outputs.append(tx_out) 59 | 60 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 61 | sighash = context.tx.signature_hash(0, input_sats, token.locking_script, sighash_flag) 62 | sig = priv_key.sign(sighash, hasher=None) 63 | sig = sig + pack_byte(sighash_flag) 64 | 65 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 66 | 67 | return token.issue( 68 | Sig(sig), 69 | PubKey(receiver), 70 | out_sats, 71 | out_sats, 72 | SigHashPreimage(preimage) 73 | ).verify(context) 74 | 75 | 76 | verify_result = test_issue(key_priv_0, key_pub_1, key_pub_0, curr_token_id + 1, curr_token_id) 77 | assert verify_result == True 78 | 79 | # Issuer must not change 80 | verify_result = test_issue(key_priv_0, key_pub_1, key_pub_1, curr_token_id + 1, curr_token_id) 81 | assert verify_result == False 82 | 83 | # Unauthorized key 84 | with pytest.raises(bitcoinx.NullFailError): 85 | test_issue(key_priv_1, key_pub_1, key_pub_0, curr_token_id + 1, curr_token_id) 86 | 87 | # Missmatched next token ID 88 | verify_result = test_issue(key_priv_0, key_pub_1, key_pub_1, curr_token_id + 2, curr_token_id) 89 | assert verify_result == False 90 | 91 | # Missmatched issued token ID 92 | verify_result = test_issue(key_priv_0, key_pub_1, key_pub_1, curr_token_id + 1, curr_token_id - 1) 93 | assert verify_result == False 94 | 95 | 96 | def test_verify_transfer(): 97 | tok_id_data = scryptlib.utils.get_push_int(curr_token_id)[1:] 98 | sender_data = scryptlib.utils.get_push_item(sender.to_bytes())[1:] 99 | 100 | token.set_data_part(scryptlib.utils.get_push_item(tok_id_data + sender_data + action_transfer)) 101 | 102 | def test_transfer(priv_key, receiver, received_tok_id=curr_token_id): 103 | context = scryptlib.utils.create_dummy_input_context() 104 | context.utxo.script_pubkey = token.locking_script 105 | context.utxo.value = input_sats 106 | 107 | new_data_part = b'\x23' + scryptlib.utils.get_push_int(received_tok_id)[1:] + \ 108 | receiver.to_bytes() + action_transfer 109 | new_locking_script = Script(token.code_part.to_bytes() + new_data_part) 110 | tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) 111 | context.tx.outputs.append(tx_out) 112 | 113 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 114 | sighash = context.tx.signature_hash(0, input_sats, token.locking_script, sighash_flag) 115 | sig = priv_key.sign(sighash, hasher=None) 116 | sig = sig + pack_byte(sighash_flag) 117 | 118 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 119 | 120 | return token.transfer( 121 | Sig(sig), 122 | PubKey(receiver), 123 | out_sats, 124 | SigHashPreimage(preimage) 125 | ).verify(context) 126 | 127 | verify_result = test_transfer(key_priv_0, key_pub_1, curr_token_id) 128 | assert verify_result == True 129 | 130 | # Unauthorized key 131 | with pytest.raises(bitcoinx.NullFailError): 132 | test_transfer(key_priv_1, key_pub_1, curr_token_id) 133 | -------------------------------------------------------------------------------- /test/test_contract_escrow.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import hashlib 3 | 4 | import scryptlib.utils 5 | import scryptlib.contract 6 | from scryptlib.types import SigHashPreimage, Int, Ripemd160, Sha256, PubKey, Sig, Bytes 7 | 8 | import bitcoinx 9 | from bitcoinx import SigHash, TxOutput, Script, PrivateKey, P2PKH_Address, Bitcoin, pack_byte 10 | 11 | 12 | contract = './test/res/escrow.scrypt' 13 | 14 | compiler_result = scryptlib.utils.compile_contract(contract) 15 | desc = compiler_result.to_desc() 16 | 17 | key_priv_A = PrivateKey.from_arbitrary_bytes(b'test0') 18 | key_pub_A = key_priv_A.public_key 19 | pkh_A = key_pub_A.hash160() 20 | 21 | key_priv_B = PrivateKey.from_arbitrary_bytes(b'test1') 22 | key_pub_B = key_priv_B.public_key 23 | pkh_B = key_pub_B.hash160() 24 | 25 | key_priv_E = PrivateKey.from_arbitrary_bytes(b'test2') 26 | key_pub_E = key_priv_E.public_key 27 | pkh_E = key_pub_E.hash160() 28 | 29 | secret0 = b'abc' 30 | secret1 = b'def' 31 | h_secret0 = hashlib.sha256(secret0).digest() 32 | h_secret1 = hashlib.sha256(secret1).digest() 33 | 34 | fee = 1000 35 | input_sats = 100000 36 | 37 | Escrow = scryptlib.contract.build_contract_class(desc) 38 | escrow = Escrow(Ripemd160(pkh_A), Ripemd160(pkh_B), Ripemd160(pkh_E), 39 | Sha256(h_secret0), Sha256(h_secret1), fee) 40 | 41 | 42 | def test_verify_scenario_1(): 43 | context = scryptlib.utils.create_dummy_input_context() 44 | context.utxo.script_pubkey = escrow.locking_script 45 | context.utxo.value = input_sats 46 | 47 | change_out = TxOutput(int(input_sats / 2 - fee), P2PKH_Address(pkh_A, Bitcoin).to_script()) 48 | context.tx.outputs.append(change_out) 49 | 50 | change_out = TxOutput(int(input_sats / 2 - fee), P2PKH_Address(pkh_B, Bitcoin).to_script()) 51 | context.tx.outputs.append(change_out) 52 | 53 | 54 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 55 | sighash = context.tx.signature_hash(0, input_sats, escrow.locking_script, sighash_flag) 56 | 57 | sig_A = key_priv_A.sign(sighash, hasher=None) 58 | sig_A = sig_A + pack_byte(sighash_flag) 59 | 60 | sig_B = key_priv_B.sign(sighash, hasher=None) 61 | sig_B = sig_B + pack_byte(sighash_flag) 62 | 63 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 64 | 65 | verify_result = escrow.unlock( 66 | SigHashPreimage(preimage), 67 | PubKey(key_pub_A), 68 | Sig(sig_A), 69 | PubKey(key_pub_B), 70 | Sig(sig_B), 71 | Bytes(b'00') 72 | ).verify(context) 73 | assert verify_result == True 74 | 75 | # Wrong preimage 76 | with pytest.raises(bitcoinx.NullFailError): 77 | escrow.unlock( 78 | SigHashPreimage(preimage + b'ff'), 79 | PubKey(key_pub_A), 80 | Sig(sig_A), 81 | PubKey(key_pub_B), 82 | Sig(sig_B), 83 | Bytes(b'00') 84 | ).verify(context) 85 | 86 | 87 | def test_verify_scenario_2(): 88 | context = scryptlib.utils.create_dummy_input_context() 89 | context.utxo.script_pubkey = escrow.locking_script 90 | context.utxo.value = input_sats 91 | 92 | change_out = TxOutput(int(input_sats - fee), P2PKH_Address(pkh_A, Bitcoin).to_script()) 93 | context.tx.outputs.append(change_out) 94 | 95 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 96 | sighash = context.tx.signature_hash(0, input_sats, escrow.locking_script, sighash_flag) 97 | 98 | sig_A = key_priv_A.sign(sighash, hasher=None) 99 | sig_A = sig_A + pack_byte(sighash_flag) 100 | 101 | sig_E = key_priv_E.sign(sighash, hasher=None) 102 | sig_E = sig_E + pack_byte(sighash_flag) 103 | 104 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 105 | 106 | verify_result = escrow.unlock( 107 | SigHashPreimage(preimage), 108 | PubKey(key_pub_A), 109 | Sig(sig_A), 110 | PubKey(key_pub_E), 111 | Sig(sig_E), 112 | Bytes(secret0) 113 | ).verify(context) 114 | assert verify_result == True 115 | 116 | # Wrong secret 117 | with pytest.raises(bitcoinx.VerifyFailed): 118 | verify_result = escrow.unlock( 119 | SigHashPreimage(preimage), 120 | PubKey(key_pub_A), 121 | Sig(sig_A), 122 | PubKey(key_pub_E), 123 | Sig(sig_E), 124 | Bytes(secret1) 125 | ).verify(context) 126 | 127 | 128 | def test_verify_scenario_3(): 129 | context = scryptlib.utils.create_dummy_input_context() 130 | context.utxo.script_pubkey = escrow.locking_script 131 | context.utxo.value = input_sats 132 | 133 | change_out = TxOutput(int(input_sats - fee), P2PKH_Address(pkh_B, Bitcoin).to_script()) 134 | context.tx.outputs.append(change_out) 135 | 136 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 137 | sighash = context.tx.signature_hash(0, input_sats, escrow.locking_script, sighash_flag) 138 | 139 | sig_B = key_priv_B.sign(sighash, hasher=None) 140 | sig_B = sig_B + pack_byte(sighash_flag) 141 | 142 | sig_E = key_priv_E.sign(sighash, hasher=None) 143 | sig_E = sig_E + pack_byte(sighash_flag) 144 | 145 | preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) 146 | 147 | verify_result = escrow.unlock( 148 | SigHashPreimage(preimage), 149 | PubKey(key_pub_B), 150 | Sig(sig_B), 151 | PubKey(key_pub_E), 152 | Sig(sig_E), 153 | Bytes(secret1) 154 | ).verify(context) 155 | assert verify_result == True 156 | 157 | -------------------------------------------------------------------------------- /scryptlib/serializer.py: -------------------------------------------------------------------------------- 1 | from bitcoinx import pack_byte, le_bytes_to_int 2 | from bitcoinx.script import ( 3 | OP_TRUE, OP_FALSE, OP_RETURN 4 | ) 5 | 6 | import scryptlib.utils as utils 7 | import scryptlib.types as types 8 | 9 | 10 | 11 | # TODO: What to do about negative integers? 12 | 13 | 14 | STATE_LEN_2BYTES = 2 15 | STATE_LEN_4BYTES = 4 16 | 17 | 18 | def encode_state_len(len_int, size_bytes): 19 | len_bytes = pack_byte(len_int) 20 | size_diff = size_bytes - len(len_bytes) 21 | 22 | if size_diff < 0: 23 | raise Exception('Size of encoding too small for passed state length. Needs at least {} additional bytes.'.fromat(size_diff * -1)) 24 | 25 | return len_bytes[::-1] + b'\x00' * size_diff 26 | 27 | 28 | def serialize_bool(val): 29 | if val: 30 | return pack_byte(OP_TRUE) 31 | return pack_byte(OP_FALSE) 32 | 33 | 34 | def serialize_int(val): 35 | return utils.get_push_int(val) 36 | 37 | 38 | def serialize_string(val): 39 | if val == '': 40 | res = pack_byte(0) 41 | res = str.encode(val, 'utf-8') 42 | return utils.get_push_item(res) 43 | 44 | 45 | def serialize_hex(val): 46 | res = bytes.fromhex(val) 47 | return utils.get_push_item(res) 48 | 49 | 50 | def serialize_bytes(val): 51 | return utils.get_push_item(val) 52 | 53 | 54 | def serialize_scrypt_type(val): 55 | if isinstance(val, types.Bool): 56 | return serialize_bool(val.value) 57 | elif isinstance(val, types.Int): 58 | return serialize_int(val.value) 59 | return bytes.fromhex(val.hex) 60 | 61 | 62 | def serialize_array(val): 63 | buff = [] 64 | for elem in val: 65 | buff.append(serialize(elem)) 66 | return b''.join(buff) 67 | 68 | 69 | def serialize_with_schema(state, key, schema): 70 | dtype = schema[key].__name__ 71 | if dtype == 'bool': 72 | return serialize_bool(state[key]) 73 | elif dtype == 'int': 74 | return serialize_int(state[key]) 75 | elif dtype == 'str': 76 | return serialize_string(state[key]) 77 | elif dtype == 'bytes': 78 | return serialize_bytes(state[key]) 79 | raise Exception('Invalid data type "{}".'.format(dtype)) 80 | 81 | 82 | def serialize(val, len_prefix=True): 83 | if isinstance(val, bool): 84 | res = serialize_bool(val) 85 | elif isinstance(val, int): 86 | res = serialize_int(val) 87 | elif isinstance(val, str): 88 | res = serialize_string(val) 89 | elif isinstance(val, bytes): 90 | res = serialize_bytes(val) 91 | elif isinstance(val, types.ScryptType): 92 | res = serialize_scrypt_type(val) 93 | elif isinstance(val, list): 94 | res = serialize_array(val) 95 | else: 96 | raise Exception('Invalid data type "{}".'.format(val.__class__)) 97 | 98 | if not len_prefix: 99 | return drop_len_prefix(res) 100 | return res 101 | 102 | 103 | def drop_len_prefix(data): 104 | assert(isinstance(data, bytes)) 105 | 106 | if len(data) < 2: 107 | return data 108 | 109 | first_byte = data[0:1] 110 | if first_byte >= b'\x01' and first_byte <= b'\x4b': 111 | return data[1:] 112 | if first_byte == b'\x4c': 113 | # OP_PUSHDATA1 114 | return data[2:] 115 | elif first_byte == b'\x4d': 116 | # OP_PUSHDATA2 117 | return data[3:] 118 | elif first_byte == b'\x4e': 119 | # OP_PUSHDATA4 120 | return data[5:] 121 | raise Exception('Invalid first byte "{}".'.format(first_byte)) 122 | 123 | 124 | 125 | def serialize_state(state, length_label_size=STATE_LEN_2BYTES, schema=None): 126 | buff = [] 127 | 128 | for key, val in state.items(): 129 | if schema: 130 | buff.append(serialize_with_schema(state, key, schema)) 131 | else: 132 | buff.append(serialize(val)) 133 | 134 | state_data = b''.join(buff) 135 | state_len = encode_state_len(len(state_data), length_label_size) 136 | return state_data + utils.get_push_item(state_len) 137 | 138 | 139 | def deserialize_state(data, schema, length_label_size=STATE_LEN_2BYTES): 140 | res = dict() 141 | 142 | if isinstance(data, types.Script): 143 | # If a Script object is passed, try to find OP_RETURN and deserialize only the part after it. 144 | # If no OP_RETURN is found, try to deserialize the whole script. 145 | data_items = [] 146 | found = False 147 | for op, item in data.ops_and_items(): 148 | if op == OP_RETURN: 149 | found = True 150 | continue 151 | if found: 152 | data_items.append((op, item)) 153 | 154 | if len(data_items) == 0: 155 | data_items = data.ops_and_items() 156 | else: 157 | data_items = iter(data_items) 158 | else: 159 | data_items = types.Script(data).ops_and_items() 160 | 161 | 162 | # Since Python 3.6 dictionaries maintain order. 163 | for key, val in schema.items(): 164 | op, item = next(data_items) 165 | if isinstance(val, bool): 166 | if item == b'': 167 | res[key] = False 168 | continue 169 | elif item == b'\x01': 170 | res[key] = True 171 | continue 172 | raise Exception('Invalid item data "{}" for boolean type.'.format(item.hex())) 173 | elif isinstance(val, int): 174 | res[key] = le_bytes_to_int(item) 175 | elif isinstance(val, str): 176 | res[key] = item.decode('utf-8') 177 | elif isinstance(val, bytes): 178 | res[key] = val 179 | else: 180 | raise Exception('Invalid value type "{}" for key "{}" in schema.'.format( 181 | val.__class__, key)) 182 | 183 | return res 184 | 185 | 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scryptlib-python 2 | A Python SDK for [sCrypt](https://scrypt.io/). 3 | 4 | [![Build Status](https://app.travis-ci.com/sCrypt-Inc/py-scryptlib.svg?branch=main)](https://travis-ci.com/sCrypt-Inc/py-scryptlib) 5 | 6 | You can learn all about writing sCrypt smart contracts in the official [docs](https://scryptdoc.readthedocs.io/en/latest/intro.html). 7 | 8 | ## Installation 9 | 10 | ### Compiler 11 | 12 | To use the SDK, you need to get a copy of the sCrypt compiler. You can get it either by downloading the [sCrypt IDE](https://scrypt.io/#download) or executing the following command, if you have an UNIX-like OS: 13 | ```sh 14 | curl -Ls https://scrypt.io/setup | sh -s -- 15 | ``` 16 | 17 | This will download the latest version of the compiler. 18 | 19 | You can also download a specific version of the compiler using the `-v` flag: 20 | ```sh 21 | curl -Ls https://scrypt.io/setup | sh -s -- -v 1.15.1 22 | ``` 23 | 24 | ### SDK 25 | 26 | You can install the SDK as a Python package using `pip`, either directly from the project folder: 27 | 28 | ```sh 29 | pip install . 30 | ``` 31 | 32 | , or from the PyPI: 33 | 34 | ```sh 35 | pip install scryptlib 36 | ``` 37 | 38 | ## Usage 39 | 40 | The SDK is used to convert script templates, produced by the sCrypt compiler, to an object-based representation in Python. It allows for easy compilation, inspection and verification of smart contracts. 41 | 42 | ### Compiling an sCrypt contract 43 | 44 | We can compile an sCrypt conract source file like so: 45 | 46 | ```python 47 | import scryptlib 48 | 49 | contract = './test/res/arraydemo.scrypt' 50 | compiler_result = scryptlib.compile_contract(contract) 51 | ``` 52 | 53 | This will leave us with a `CompilerResult` object, that contains all of the data, returned by the compiler. 54 | The `compile_contract` method will try to automatically search for the compiler binary. You can also explicitly pass the path to the binary, using the `compiler_bin` parameter. 55 | 56 | It is also possible to pass the sCrypt source code as a string object: 57 | 58 | ```python 59 | contract_source = ''' 60 | contract Equals { 61 | int x; 62 | 63 | constructor(int x) { 64 | this.x = x; 65 | } 66 | 67 | public function equals(int y) { 68 | require(this.x == y); 69 | } 70 | 71 | } 72 | ''' 73 | 74 | compiler_result = scryptlib.compile_contract(contract_source, from_string=True) 75 | ``` 76 | 77 | The resulting **contract description file** will be written to `./out` by default. That may also be changed with the `out_dir` parameter. 78 | You can access the contract description directly from the `CompilerResult` with its `to_desc()` method. 79 | 80 | ### Evaluating a contract locally 81 | 82 | We can evaluate any public function of the contract locally on our machine, before broadcasting it. 83 | 84 | First we need to create a class representation of the contract and instantiate it: 85 | 86 | ```python 87 | import scryptlib 88 | from scryptlib.types import Int 89 | 90 | EQUAL_VAL = 2021 91 | 92 | contract = './test/res/equals.scrypt' 93 | 94 | compiler_result = scryptlib.compile_contract(contract) 95 | desc = compiler_result.to_desc() 96 | 97 | Equals = scryptlib.build_contract_class(desc) 98 | contract_obj = Equals(Int(EQUAL_VAL)) 99 | ``` 100 | 101 | As we can see, the created class takes the contract parameters in the constructor. In the case of the `Equals` contract, that is an sCrypt `int` type, which we can represent in Python using an instance of `scryptlib.types.Int`. 102 | 103 | Once we have an instance of the contract class, we can evaluate its public functions: 104 | 105 | ```python 106 | verify_result = contract_obj.equals(Int(EQUAL_VAL)).verify() 107 | assert verify_result == True 108 | ``` 109 | 110 | From the example see, that we called the contracts public function, named `equals`. The actual call to `equals()` in Python reutrns an instance of `scryptlib.abi.FunctionCall`. That object in turn has a method, named `verify`, with which we can run the function calls unlocking script against the contracts locking script. 111 | `verify` can internaly create an input evaluation context for simple contracts, but once we start using more advanced constructs, like signatures, we can pass an instance of `bitconx.TxInputContext`, using the `tx_input_context` parameter. 112 | 113 | py-scryptlib leverages the [bitcoinx](https://github.com/kyuupichan/bitcoinX) library to deal with Bitcoin primitives. 114 | 115 | The following is an example of a local evaluation of a P2PKH contract: 116 | 117 | ```python 118 | import pytest 119 | import scryptlib 120 | from scryptlib.types import Sig, PubKey, Ripemd160 121 | 122 | import bitcoinx 123 | from bitcoinx import SigHash, PrivateKey, pack_byte 124 | 125 | 126 | key_priv = PrivateKey.from_arbitrary_bytes(b'test123') 127 | key_pub = key_priv.public_key 128 | pubkey_hash = key_pub.hash160() 129 | 130 | contract = './test/res/p2pkh.scrypt' 131 | 132 | compiler_result = scryptlib.compile_contract(contract) 133 | desc = compiler_result.to_desc() 134 | 135 | P2PKH = scryptlib.build_contract_class(desc) 136 | p2pkh_obj = P2PKH(Ripemd160(pubkey_hash)) 137 | 138 | context = scryptlib.create_dummy_input_context() 139 | 140 | sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) 141 | input_idx = 0 142 | utxo_satoshis = context.utxo.value 143 | sighash = context.tx.signature_hash(input_idx, utxo_satoshis, p2pkh_obj.locking_script, sighash_flag) 144 | 145 | sig = key_priv.sign(sighash, hasher=None) 146 | sig = sig + pack_byte(sighash_flag) 147 | 148 | verify_result = p2pkh_obj.unlock(Sig(sig), PubKey(key_pub)).verify(context) 149 | assert verify_result == True 150 | ``` 151 | 152 | You can find many other examples under `test/test_contract_*`. 153 | 154 | ## Testing 155 | 156 | The SDK has a suite of unit tests, which we can run by executing the `pytest` command in the root of the project. 157 | -------------------------------------------------------------------------------- /test/test_contract_structdemo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Bytes 6 | 7 | import bitcoinx 8 | 9 | 10 | contract = './test/res/structdemo.scrypt' 11 | 12 | compiler_result = scryptlib.utils.compile_contract(contract) 13 | desc = compiler_result.to_desc() 14 | 15 | StructDemo = scryptlib.contract.build_contract_class(desc) 16 | type_classes = scryptlib.contract.build_type_classes(desc) 17 | 18 | Person = type_classes['Person'] 19 | Pet = type_classes['Pet'] 20 | Female = type_classes['Female'] 21 | 22 | struct_demo = StructDemo(Person({ 23 | 'name': Bytes('satoshi nakamoto'.encode('ascii')), 24 | 'leftHanded': False, 25 | 'age': 33, 26 | 'addr': Bytes('hello, world!'.encode('ascii')), 27 | 'pets': [ 28 | Pet( 29 | {'name': Bytes('kala'.encode('ascii')), 30 | 'species': Bytes('dog'.encode('ascii'))} 31 | ), 32 | Pet( 33 | {'name': Bytes('pufi'.encode('ascii')), 34 | 'species': Bytes('cat'.encode('ascii'))} 35 | ) 36 | ] 37 | })) 38 | 39 | def test_correct(): 40 | verify_result = struct_demo.main(Person({ 41 | 'name': Bytes('satoshi nakamoto'.encode('ascii')), 42 | 'leftHanded': False, 43 | 'age': 33, 44 | 'addr': Bytes('hello, world!'.encode('ascii')), 45 | 'pets': [ 46 | Pet( 47 | {'name': Bytes('kala'.encode('ascii')), 48 | 'species': Bytes('dog'.encode('ascii'))} 49 | ), 50 | Pet( 51 | {'name': Bytes('pufi'.encode('ascii')), 52 | 'species': Bytes('cat'.encode('ascii'))} 53 | ) 54 | ] 55 | })).verify() 56 | assert verify_result == True 57 | 58 | def test_wrong_value(): 59 | with pytest.raises(bitcoinx.VerifyFailed): 60 | verify_result = struct_demo.main(Person({ 61 | 'name': Bytes('satoshi nakamoto'.encode('ascii')), 62 | 'leftHanded': True, 63 | 'age': 33, 64 | 'addr': Bytes('hello, world!'.encode('ascii')), 65 | 'pets': [ 66 | Pet( 67 | {'name': Bytes('kala'.encode('ascii')), 68 | 'species': Bytes('dog'.encode('ascii'))} 69 | ), 70 | Pet( 71 | {'name': Bytes('pufi'.encode('ascii')), 72 | 'species': Bytes('cat'.encode('ascii'))} 73 | ) 74 | ] 75 | })).verify() 76 | 77 | def test_wrong_value2(): 78 | with pytest.raises(bitcoinx.VerifyFailed): 79 | verify_result = struct_demo.main(Person({ 80 | 'name': Bytes('satoshi nakamoto'.encode('ascii')), 81 | 'leftHanded': True, 82 | 'age': 34, 83 | 'addr': Bytes('hello, world!'.encode('ascii')), 84 | 'pets': [ 85 | Pet( 86 | {'name': Bytes('kala'.encode('ascii')), 87 | 'species': Bytes('dog'.encode('ascii'))} 88 | ), 89 | Pet( 90 | {'name': Bytes('pufi'.encode('ascii')), 91 | 'species': Bytes('cat'.encode('ascii'))} 92 | ) 93 | ] 94 | })).verify() 95 | 96 | def test_wrong_value3(): 97 | with pytest.raises(bitcoinx.VerifyFailed): 98 | verify_result = struct_demo.main(Person({ 99 | 'name': Bytes('satoshi nakamoto'.encode('ascii')), 100 | 'leftHanded': False, 101 | 'age': 33, 102 | 'addr': Bytes('hellou, world!'.encode('ascii')), 103 | 'pets': [ 104 | Pet( 105 | {'name': Bytes('kala'.encode('ascii')), 106 | 'species': Bytes('dog'.encode('ascii'))} 107 | ), 108 | Pet( 109 | {'name': Bytes('pufi'.encode('ascii')), 110 | 'species': Bytes('cat'.encode('ascii'))} 111 | ) 112 | ] 113 | })).verify() 114 | 115 | def test_wrong_value4(): 116 | with pytest.raises(bitcoinx.VerifyFailed): 117 | verify_result = struct_demo.main(Person({ 118 | 'name': Bytes('satoshi nakamoto'.encode('ascii')), 119 | 'leftHanded': False, 120 | 'age': 33, 121 | 'addr': Bytes('hello, world!'.encode('ascii')), 122 | 'pets': [ 123 | Pet( 124 | {'name': Bytes('kuki'.encode('ascii')), 125 | 'species': Bytes('dog'.encode('ascii'))} 126 | ), 127 | Pet( 128 | {'name': Bytes('pufi'.encode('ascii')), 129 | 'species': Bytes('cat'.encode('ascii'))} 130 | ) 131 | ] 132 | })).verify() 133 | 134 | def test_type_alias(): 135 | verify_result = struct_demo.main(Female({ 136 | 'name': Bytes('satoshi nakamoto'.encode('ascii')), 137 | 'leftHanded': False, 138 | 'age': 33, 139 | 'addr': Bytes('hello, world!'.encode('ascii')), 140 | 'pets': [ 141 | Pet( 142 | {'name': Bytes('kala'.encode('ascii')), 143 | 'species': Bytes('dog'.encode('ascii'))} 144 | ), 145 | Pet( 146 | {'name': Bytes('pufi'.encode('ascii')), 147 | 'species': Bytes('cat'.encode('ascii'))} 148 | ) 149 | ] 150 | })).verify() 151 | assert verify_result == True 152 | 153 | -------------------------------------------------------------------------------- /test/res/serializer.scrypt: -------------------------------------------------------------------------------- 1 | import "./util.scrypt"; 2 | 3 | // a de/serializer for basic types 4 | 5 | library Reader { 6 | // fixed number of bytes to denote length serialized state, including varint prefix (1 byte) + length (2 bytes) 7 | // change this length to 4 when you need PushData4 8 | static const int StateLen = 3; 9 | 10 | bytes buf; 11 | int pos; 12 | 13 | constructor(bytes buf) { 14 | this.buf = buf; 15 | this.pos = 0; 16 | } 17 | 18 | function eof() : bool { 19 | return this.pos >= len(this.buf); 20 | } 21 | 22 | function readBytes() : bytes { 23 | int len = 0; 24 | bytes b = this.buf; 25 | bytes ret = b''; 26 | int header = unpack(b[this.pos : this.pos + 1]); 27 | this.pos++; 28 | 29 | if (header < 0x4c) { 30 | len = header; 31 | ret = b[this.pos : this.pos + len]; 32 | } 33 | else if (header == 0x4c) { 34 | len = Util.fromLEUnsigned(b[this.pos : this.pos + 1]); 35 | this.pos += 1; 36 | ret = b[this.pos : this.pos + len]; 37 | } 38 | else if (header == 0x4d) { 39 | len = Util.fromLEUnsigned(b[this.pos : this.pos + 2]); 40 | this.pos += 2; 41 | ret = b[this.pos : this.pos + len]; 42 | } 43 | else if (header == 0x4e) { 44 | len = Util.fromLEUnsigned(b[this.pos : this.pos + 4]); 45 | this.pos += 4; 46 | ret = b[this.pos : this.pos + len]; 47 | } 48 | else { 49 | // shall not reach here 50 | require(false); 51 | } 52 | 53 | this.pos += len; 54 | return ret; 55 | } 56 | 57 | function readBool() : bool { 58 | bytes b = this.buf[this.pos : this.pos + 1]; 59 | this.pos++; 60 | return OpCode.OP_0 != b; 61 | } 62 | 63 | function readInt() : int { 64 | return unpack(this.readBytes()); 65 | } 66 | 67 | static function getStateStart(bytes scriptCode) : int { 68 | // locking script: code + opreturn + data(state + state_len) 69 | int scriptLen = len(scriptCode); 70 | // read state length: +1 to skip varint prefix 71 | bytes lb = scriptCode[scriptLen - Reader.StateLen + 1 :]; 72 | int stateLen = unpack(lb); 73 | return scriptLen - stateLen - Reader.StateLen; 74 | } 75 | } 76 | 77 | library Writer { 78 | // return VarInt encoding 79 | static function writeBytes(bytes b) : bytes { 80 | int n = len(b); 81 | 82 | bytes header = b''; 83 | 84 | if (n < 0x4c) { 85 | header = Util.toLEUnsigned(n, 1); 86 | } 87 | else if (n < 0x100) { 88 | header = b'4c' + Util.toLEUnsigned(n, 1); 89 | } 90 | else if (n < 0x10000) { 91 | header = b'4d' + Util.toLEUnsigned(n, 2); 92 | } 93 | else if (n < 0x100000000) { 94 | header = b'4e' + Util.toLEUnsigned(n, 4); 95 | } 96 | else { 97 | // shall not reach here 98 | require(false); 99 | } 100 | 101 | return header + b; 102 | } 103 | 104 | // uses fixed 1 byte to represent a boolean, plus length 105 | static function writeBool(bool x) : bytes { 106 | return x ? OpCode.OP_1 : OpCode.OP_0; 107 | } 108 | 109 | // int is little endian 110 | static function writeInt(int x) : bytes { 111 | return writeBytes(pack(x)); 112 | } 113 | 114 | static function serializeState(bytes stateBuf) : bytes { 115 | // serialize state size 116 | bytes lenBuf = Writer.writeBytes(num2bin(len(stateBuf), Reader.StateLen - 1 /* varint prefix byte */)); 117 | return stateBuf + lenBuf; 118 | } 119 | } 120 | 121 | contract Test { 122 | public function testBool(bool f) { 123 | bytes buf = Writer.writeBool(f); 124 | 125 | Reader r = new Reader(buf); 126 | bool f_ = r.readBool(); 127 | require(f_ == f); 128 | require(r.eof()); 129 | } 130 | public function testBytes(bytes b) { 131 | bytes buf = Writer.writeBytes(b); 132 | 133 | Reader r = new Reader(buf); 134 | bytes b_ = r.readBytes(); 135 | require(b_ == b); 136 | require(r.eof()); 137 | } 138 | 139 | public function testInt(int i) { 140 | bytes buf = Writer.writeInt(i); 141 | 142 | Reader r = new Reader(buf); 143 | int i_ = r.readInt(); 144 | require(i_ == i); 145 | require(r.eof()); 146 | } 147 | 148 | public function main(bool f, bytes b, int i) { 149 | { 150 | bytes buf = Writer.writeBool(f); 151 | 152 | Reader r = new Reader(buf); 153 | bool f_ = r.readBool(); 154 | require(f_ == f); 155 | require(r.eof()); 156 | } 157 | { 158 | bytes buf = Writer.writeBytes(b); 159 | 160 | Reader r = new Reader(buf); 161 | bytes b_ = r.readBytes(); 162 | require(b_ == b); 163 | require(r.eof()); 164 | } 165 | { 166 | bytes buf = Writer.writeInt(i); 167 | 168 | Reader r = new Reader(buf); 169 | int i_ = r.readInt(); 170 | require(i_ == i); 171 | require(r.eof()); 172 | } 173 | 174 | bytes buf = Writer.writeInt(i) + Writer.writeBytes(b) + Writer.writeBytes(b) + Writer.writeBool(f) + Writer.writeInt(i) + Writer.writeBytes(b); 175 | 176 | Reader r = new Reader(buf); 177 | 178 | int i_ = r.readInt(); 179 | require(i_ == i); 180 | require(!r.eof()); 181 | bytes b_ = r.readBytes(); 182 | require(b_ == b); 183 | b_ = r.readBytes(); 184 | require(b_ == b); 185 | bool f_ = r.readBool(); 186 | require(f_ == f); 187 | i_ = r.readInt(); 188 | require(i_ == i); 189 | b_ = r.readBytes(); 190 | require(b_ == b); 191 | require(r.eof()); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /test/res/arraydemo.scrypt: -------------------------------------------------------------------------------- 1 | struct ST1 { 2 | bool x; 3 | bytes y; 4 | int i; 5 | } 6 | 7 | struct ST2 { 8 | bool x; 9 | bytes y; 10 | ST3 st3; 11 | } 12 | 13 | struct ST3 { 14 | bool x; 15 | int[3] y; 16 | ST1 st1; 17 | } 18 | 19 | type AliasST2 = ST2; 20 | 21 | type MDArrayST1 = ST1[2][2][2]; 22 | 23 | contract ArrayDemo { 24 | int m; 25 | bool[2] arr_bool; 26 | int[2] arr_int; 27 | Ripemd160[2] arr_Ripemd160; 28 | 29 | int[2][3][4] X; 30 | MDArrayST1 stArray; 31 | 32 | public function testArrayConstructor(int m, bool[2] arr1, int[2] arr2, Ripemd160[2] arr3) { 33 | 34 | require(this.m == m); 35 | require(this.arr_bool[0] == arr1[0]); 36 | require(this.arr_bool[1] == arr1[1]); 37 | require(this.arr_int[0] == arr2[0]); 38 | require(this.arr_int[1] == arr2[1]); 39 | require(this.arr_Ripemd160[0] == arr3[0]); 40 | require(this.arr_Ripemd160[1] == arr3[1]); 41 | } 42 | 43 | public function testArrayInt(int[4] arr) { 44 | 45 | require(arr[0] == 0); 46 | require(arr[1] == 1321); 47 | require(arr[2] == 243213); 48 | require(arr[3] == 32132); 49 | } 50 | 51 | public function testArrayBool(bool[5] arr) { 52 | require(this.arr_bool[0] == arr[0]); 53 | require(arr[1]); 54 | require(!arr[2]); 55 | require(arr[3]); 56 | require(arr[4]); 57 | } 58 | 59 | public function testArrayRipemd160(Ripemd160[2] arr) { 60 | require(arr[0] == b'0176de27477fb7ffd7c99a7e9b931c22fd125c2b'); 61 | require(arr[1] == b'0176de27477fb7ffd7c99a7e9b931c22fd125c2b'); 62 | } 63 | 64 | public function testArraySig(Sig[2] arr) { 65 | require(arr[0] == b'30440220349eb89c004114bf238ea1b5db996b709675a9446aa33677f2848e839d64dfe2022046af3cf48ef13855594e7cc8c31771c5b159af19ea077b9c986beacf9a43791841'); 66 | require(arr[1] == b'30440220349eb89c004114bf238ea1b5db996b709675a9446aa33677f2848e839d64dfe2022046af3cf48ef13855594e7cc8c31771c5b159af19ea077b9c986beacf9a437918414444'); 67 | } 68 | 69 | public function unlock(int[2][3] P1, int[2] P2) { 70 | 71 | int x = P1[0][1]; 72 | 73 | require(x == 1); 74 | 75 | require(P2[0] == 1); 76 | require(P2[1] == 32); 77 | 78 | require(P1[0][0] == 3); 79 | require(P1[0][1] == 1); 80 | require(P1[0][2] == 2); 81 | require(P1[1][0] == 4); 82 | require(P1[1][1] == 5); 83 | require(P1[1][2] == 6); 84 | 85 | require(P1[0] == [3, 1, 2]); 86 | require(P1[1] == [4, 5, 6]); 87 | 88 | require(this.X[0][0][0] == 1); 89 | require(this.X[0][0][1] == 2); 90 | require(this.X[0][0][2] == 3); 91 | require(this.X[0][0][3] == 4); 92 | require(this.X[0][1][0] == 5); 93 | require(this.X[0][2][3] == 12); 94 | require(this.X[1][0][0] == 13); 95 | require(this.X[1][1][1] == 18); 96 | 97 | require(this.X[x][x + 1][x + 2] == 24); 98 | 99 | // with parens 100 | require((this.X[1])[2][x + 2] == 24); 101 | require((this.X[1][2])[x + 2] == 24); 102 | 103 | require(this.X[0] == [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]); 104 | 105 | require(this.X[0][1] == [5, 6, 7, 8]); 106 | } 107 | 108 | public function unlockST1(ST1[2] st1array) { 109 | ST1 st0 = st1array[0]; 110 | ST1 st1 = st1array[1]; 111 | require(st0.x == false); 112 | require(st0.y == b'68656c6c6f20776f726c6421'); 113 | require(st0.i == 1); 114 | require(st1.x == true); 115 | require(st1.y == b'68656c6c6f20776f726c6420'); 116 | require(st1.i == 2); 117 | } 118 | 119 | public function unlockAliasST2(AliasST2[2] st1array) { 120 | AliasST2 st0 = st1array[0]; 121 | AliasST2 st1 = st1array[1]; 122 | require(st0.x == false); 123 | require(st0.y == b'68656c6c6f20776f726c6421'); 124 | require(st0.st3.x == false); 125 | require(st0.st3.y[0] == 1); 126 | require(st0.st3.y[1] == 2); 127 | require(st0.st3.y[2] == 3); 128 | require(st0.st3.st1.x == false); 129 | require(st0.st3.st1.y == b'68656e'); 130 | require(st0.st3.st1.i == 11); 131 | 132 | require(st1.x == true); 133 | require(st1.y == b'68656c6c6f20776f726c6420'); 134 | require(st1.st3.x == true); 135 | require(st1.st3.y[0] == 4); 136 | require(st1.st3.y[1] == 5); 137 | require(st1.st3.y[2] == 6); 138 | require(st1.st3.st1.x == true); 139 | require(st1.st3.st1.y == b'6420'); 140 | require(st1.st3.st1.i == 12); 141 | } 142 | 143 | public function unlockST2(ST2 st2) { 144 | 145 | require(st2.x == true); 146 | require(st2.y == b'68656c6c6f20776f726c6420'); 147 | require(st2.st3.x == true); 148 | require(st2.st3.y[0] == 4); 149 | require(st2.st3.y[1] == 5); 150 | require(st2.st3.y[2] == 6); 151 | require(st2.st3.st1.x == false); 152 | require(st2.st3.st1.y == b'68656c6c6f20776f726c6420'); 153 | require(st2.st3.st1.i == 42); 154 | } 155 | 156 | public function unlockMDArrayST1(MDArrayST1 st1mdarray) { 157 | require(st1mdarray[0][0][0] == { false, b'aa', 1 }); 158 | require(st1mdarray[0][0][0].x == false); 159 | require(st1mdarray[0][0][0].y == b'aa'); 160 | require(st1mdarray[0][0][0].i == 1); 161 | 162 | require(st1mdarray[0][0][1] == { true, b'bb', 2 }); 163 | require(st1mdarray[0][0][1].x == true); 164 | require(st1mdarray[0][0][1].y == b'bb'); 165 | require(st1mdarray[0][0][1].i == 2); 166 | 167 | require(st1mdarray[0][0] == [{ false, b'aa', 1 }, { true, b'bb', 2 }]); 168 | 169 | require(st1mdarray[0][1][0] == { false, b'cc', 3 }); 170 | require(st1mdarray[0][1][0].x == false); 171 | require(st1mdarray[0][1][0].y == b'cc'); 172 | require(st1mdarray[0][1][0].i == 3); 173 | 174 | require(st1mdarray[1][1][1] == { true, b'11', 8 }); 175 | require(st1mdarray[1][1][1].x == true); 176 | require(st1mdarray[1][1][1].y == b'11'); 177 | require(st1mdarray[1][1][1].i == 8); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /test/test_contract_arraydemo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import scryptlib.utils 4 | import scryptlib.contract 5 | from scryptlib.types import Ripemd160, Bytes, Sig 6 | 7 | import bitcoinx 8 | 9 | 10 | contract = './test/res/arraydemo.scrypt' 11 | 12 | compiler_result = scryptlib.utils.compile_contract(contract) 13 | desc = compiler_result.to_desc() 14 | 15 | ArrayDemo = scryptlib.contract.build_contract_class(desc) 16 | 17 | type_classes = scryptlib.contract.build_type_classes(desc) 18 | ST1 = type_classes['ST1'] 19 | ST2 = type_classes['ST2'] 20 | ST3 = type_classes['ST3'] 21 | AliasST2 = type_classes['AliasST2'] 22 | MDArrayST1 = type_classes['MDArrayST1'] 23 | 24 | arraydemo = ArrayDemo(33, [ 25 | True, 26 | False 27 | ], [ 28 | 3311, 29 | 333 30 | ], [ 31 | Ripemd160('2235c953af7c83cffa6f192477fb431941400162'), 32 | Ripemd160('0176de27477fb7ffd7c99a7e9b931c22fd125c2b') 33 | ], [ 34 | [ 35 | [ 36 | 1, 2, 3, 4 37 | ], 38 | [ 39 | 5, 6, 7, 8 40 | ], 41 | [ 42 | 9, 10, 11, 12 43 | ] 44 | ], 45 | [ 46 | [ 47 | 13, 14, 15, 16 48 | ], 49 | [ 50 | 17, 18, 19, 20 51 | ], 52 | [ 53 | 21, 22, 23, 24 54 | ] 55 | ] 56 | ], 57 | [[[ST1({ 58 | 'x': False, 59 | 'y': Bytes('aa'), 60 | 'i': 1 61 | }), ST1({ 62 | 'y': Bytes('bb'), 63 | 'x': True, 64 | 'i': 2 65 | })], [ST1({ 66 | 'x': False, 67 | 'y': Bytes('cc'), 68 | 'i': 3 69 | }), ST1({ 70 | 'y': Bytes('dd'), 71 | 'x': True, 72 | 'i': 4 73 | })]], [[ST1({ 74 | 'x': False, 75 | 'y': Bytes('ee'), 76 | 'i': 5 77 | }), ST1({ 78 | 'y': Bytes('ff'), 79 | 'x': True, 80 | 'i': 6 81 | })], [ST1({ 82 | 'x': False, 83 | 'y': Bytes('00'), 84 | 'i': 7 85 | }), ST1({ 86 | 'y': Bytes('11'), 87 | 'x': True, 88 | 'i': 8 89 | })]]] 90 | ) 91 | 92 | 93 | def test_array_constructor_correct(): 94 | verify_result = arraydemo.testArrayConstructor( 95 | 33, 96 | [ 97 | True, 98 | False 99 | ], 100 | [ 101 | 3311, 102 | 333 103 | ], 104 | [ 105 | Ripemd160('2235c953af7c83cffa6f192477fb431941400162'), 106 | Ripemd160('0176de27477fb7ffd7c99a7e9b931c22fd125c2b') 107 | ] 108 | ).verify() 109 | assert verify_result == True 110 | 111 | 112 | def test_array_constructor_wrong_val(): 113 | with pytest.raises(bitcoinx.VerifyFailed): 114 | arraydemo.testArrayConstructor( 115 | 33, 116 | [ 117 | True, 118 | False 119 | ], 120 | [ 121 | 3312, 122 | 333 123 | ], 124 | [ 125 | Ripemd160('2235c953af7c83cffa6f192477fb431941400162'), 126 | Ripemd160('0176de27477fb7ffd7c99a7e9b931c22fd125c2b') 127 | ] 128 | ).verify() 129 | 130 | 131 | def test_array_constructor_wrong_params(): 132 | with pytest.raises(Exception): 133 | arraydemo.testArrayConstructor( 134 | True, 135 | [ 136 | True, 137 | False 138 | ], 139 | [ 140 | False 141 | ], 142 | [ 143 | Ripemd160('2235c953af7c83cffa6f192477fb431941400162'), 144 | Ripemd160('0176de27477fb7ffd7c99a7e9b931c22fd125c2b') 145 | ] 146 | ).verify() 147 | 148 | 149 | def test_unlock_ST2_correct(): 150 | verify_result = arraydemo.unlockST2(ST2( 151 | { 152 | 'x': True, 153 | 'y': Bytes('68656c6c6f20776f726c6420'), 154 | 'st3': ST3({ 155 | 'x': True, 156 | 'y': [4, 5, 6], 157 | 'st1': ST1({ 158 | 'x': False, 159 | 'y': Bytes('68656c6c6f20776f726c6420'), 160 | 'i': 42 161 | }) 162 | }) 163 | }) 164 | ).verify() 165 | assert verify_result == True 166 | 167 | 168 | def test_array_int_correct(): 169 | verify_result = arraydemo.testArrayInt([0, 1321, 243213, 32132]).verify() 170 | assert verify_result == True 171 | 172 | 173 | def test_array_bool_correct(): 174 | verify_result = arraydemo.testArrayBool([True, True, False, True, True]).verify() 175 | assert verify_result == True 176 | 177 | 178 | def test_array_ripemd160_correct(): 179 | verify_result = arraydemo.testArrayRipemd160([ 180 | Ripemd160('0176de27477fb7ffd7c99a7e9b931c22fd125c2b'), 181 | Ripemd160('0176de27477fb7ffd7c99a7e9b931c22fd125c2b')]).verify() 182 | assert verify_result == True 183 | 184 | 185 | def test_array_sig_correct(): 186 | verify_result = arraydemo.testArraySig([ 187 | Sig('30440220349eb89c004114bf238ea1b5db996b709675a9446aa33677f2848e839d64dfe2022046af3cf48ef13855594e7cc8c31771c5b159af19ea077b9c986beacf9a43791841'), 188 | Sig('30440220349eb89c004114bf238ea1b5db996b709675a9446aa33677f2848e839d64dfe2022046af3cf48ef13855594e7cc8c31771c5b159af19ea077b9c986beacf9a437918414444')]).verify() 189 | assert verify_result == True 190 | 191 | 192 | def test_unlock_correct(): 193 | verify_result = arraydemo.unlock([ 194 | [ 195 | 3, 1, 2 196 | ], 197 | [4, 5, 6] 198 | ], 199 | [ 200 | 1, 32 201 | ] 202 | ).verify() 203 | assert verify_result == True 204 | 205 | --------------------------------------------------------------------------------