├── .github └── CODEOWNERS ├── .gitignore ├── .gitmodules ├── .travis.yml ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README-zh.md ├── README.md ├── build.rs ├── c ├── anyone_can_pay_lock.h ├── bech32.h ├── blake2b.h ├── ckb_cell_upgrade.c ├── common.h ├── defs.h ├── dump_secp256k1_data.c ├── keccak256.h ├── overflow_add.h ├── quick_pow10.h ├── secp256k1_helper.h ├── secp256k1_keccak256_lock.h ├── secp256k1_keccak256_sighash_all.c ├── secp256k1_keccak256_sighash_all_acpl.c └── utils.h ├── deps └── molecule │ ├── VERSION │ ├── molecule_builder.h │ └── molecule_reader.h ├── specs └── cells │ └── .gitkeep └── src ├── lib.rs └── tests ├── anyone_can_pay.rs ├── mod.rs ├── secp256k1_keccak256_sighash_all.rs └── secp256k1_keccak256_sighash_all_acpl_compatibility.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nervosnetwork/ckb-code-review 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | build/ 55 | c/blockchain.mol 56 | c/protocol.h 57 | 58 | target 59 | ./tmp 60 | .idea/ 61 | /specs/cells 62 | .vscode/ 63 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/secp256k1"] 2 | path = deps/secp256k1 3 | url = https://github.com/nervosnetwork/secp256k1 4 | [submodule "deps/ckb-c-std-lib"] 5 | path = deps/ckb-c-std-lib 6 | url = https://github.com/nervosnetwork/ckb-c-stdlib.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 1.34.2 3 | dist: xenial 4 | sudo: true 5 | cache: 6 | directories: 7 | - $HOME/.cargo 8 | timeout: 1024 9 | 10 | git: 11 | depth: 2 12 | 13 | env: 14 | global: 15 | - RUST_BACKTRACE=full 16 | 17 | matrix: 18 | include: 19 | - name: Test 20 | script: 21 | - make install-tools 22 | - make generate-protocol 23 | - make all-via-docker 24 | - cargo test --tests 25 | 26 | - name: Publish 27 | if: 'tag IS present AND env(CRATES_IO_TOKEN) IS present' 28 | script: 29 | - make install-tools 30 | - make generate-protocol 31 | - make all-via-docker 32 | - cargo login $CRATES_IO_TOKEN 33 | - make publish 34 | 35 | before_cache: 36 | - rm -rf ~/.cargo/registry 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.cjson": "jsonc", 4 | "*.wxss": "css", 5 | "*.wxs": "javascript", 6 | ".gitmodules": "gitconfig", 7 | "*.vue": "vue", 8 | "secp256k1_helper.h": "c", 9 | "blake2b.h": "c", 10 | "common.h": "c", 11 | "__locale": "cpp", 12 | "__string": "cpp", 13 | "string": "cpp", 14 | "string_view": "cpp", 15 | "cmath": "c", 16 | "complex": "c", 17 | "keccak256.h": "c", 18 | "ckb_syscalls.h": "c", 19 | "protocol.h": "c", 20 | "string.h": "c", 21 | "memory": "c", 22 | "bech32.h": "c", 23 | "molecule_reader.h": "c", 24 | "defs.h": "c", 25 | "ckb_consts.h": "c", 26 | "secp256k1_data_info.h": "c", 27 | "bitset": "cpp", 28 | "functional": "cpp", 29 | "iterator": "cpp", 30 | "basic-config.h": "c", 31 | "main_impl.h": "c", 32 | "secp256k1_recovery.h": "c" 33 | } 34 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pw-lock-scripts" 3 | version = "0.5.1" 4 | authors = ["Lay2 Dev "] 5 | edition = "2018" 6 | build = "build.rs" 7 | license = "MIT" 8 | description = "PW Lock Scripts" 9 | repository = "https://github.com/lay2dev/pw-lock" 10 | include = ["src/**/*", "Cargo.toml", "build.rs", "specs/cells/*"] 11 | 12 | [dependencies] 13 | includedir = "0.5.0" 14 | phf = "0.7.21" 15 | sha3 = "0.8.2" 16 | bech32 = "0.7.2" 17 | 18 | [build-dependencies] 19 | includedir_codegen = "0.5.0" 20 | blake2b-rs = "0.1.5" 21 | faster-hex = "0.3" 22 | 23 | [dev-dependencies] 24 | byteorder = "1.3.1" 25 | ckb-types = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 26 | ckb-script = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 27 | ckb-crypto = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 28 | ckb-dao-utils = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 29 | ckb-hash = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 30 | ckb-error = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 31 | ckb-fixed-hash = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 32 | rand = "0.6.5" 33 | lazy_static = "1.3.0" 34 | ripemd160 = "0.8.0" 35 | sha2 = "0.8.0" 36 | secp256k1 = { version = "0.15.1" } 37 | faster-hex = "0.3" 38 | 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := riscv64-unknown-elf 2 | CC := $(TARGET)-gcc 3 | LD := $(TARGET)-gcc 4 | OBJCOPY := $(TARGET)-objcopy 5 | CFLAGS := -O3 -Ideps/molecule -I deps/secp256k1/src -I deps/secp256k1 -I deps/ckb-c-std-lib -I c -I build -Wall -Werror -Wno-nonnull-compare -Wno-unused-function -g 6 | LDFLAGS := -Wl,-static -fdata-sections -ffunction-sections -Wl,--gc-sections 7 | SECP256K1_SRC := deps/secp256k1/src/ecmult_static_pre_context.h 8 | MOLC := moleculec 9 | MOLC_VERSION := 0.4.1 10 | PROTOCOL_HEADER := c/protocol.h 11 | PROTOCOL_SCHEMA := c/blockchain.mol 12 | PROTOCOL_VERSION := d75e4c56ffa40e17fd2fe477da3f98c5578edcd1 13 | PROTOCOL_URL := https://raw.githubusercontent.com/nervosnetwork/ckb/${PROTOCOL_VERSION}/util/types/schemas/blockchain.mol 14 | 15 | # docker pull nervos/ckb-riscv-gnu-toolchain:bionic-20190702 16 | BUILDER_DOCKER := nervos/ckb-riscv-gnu-toolchain@sha256:7b168b4b109a0f741078a71b7c4dddaf1d283a5244608f7851f5714fbad273ba 17 | 18 | all: specs/cells/ckb_cell_upgrade specs/cells/secp256k1_keccak256_sighash_all specs/cells/secp256k1_keccak256_sighash_all_acpl 19 | 20 | all-via-docker: ${PROTOCOL_HEADER} 21 | docker run --rm -v `pwd`:/code ${BUILDER_DOCKER} bash -c "cd /code && make" 22 | 23 | specs/cells/secp256k1_keccak256_sighash_all: c/secp256k1_keccak256_sighash_all.c ${PROTOCOL_HEADER} c/keccak256.h c/common.h c/utils.h build/secp256k1_data_info.h $(SECP256K1_SRC) 24 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< 25 | $(OBJCOPY) --only-keep-debug $@ $(subst specs/cells,build,$@.debug) 26 | $(OBJCOPY) --strip-debug --strip-all $@ 27 | 28 | specs/cells/secp256k1_keccak256_sighash_all_acpl: c/secp256k1_keccak256_sighash_all_acpl.c ${PROTOCOL_HEADER} c/keccak256.h c/common.h c/utils.h build/secp256k1_data_info.h $(SECP256K1_SRC) 29 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< 30 | $(OBJCOPY) --only-keep-debug $@ $(subst specs/cells,build,$@.debug) 31 | $(OBJCOPY) --strip-debug --strip-all $@ 32 | 33 | specs/cells/ckb_cell_upgrade: c/ckb_cell_upgrade.c ${PROTOCOL_HEADER} 34 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< 35 | $(OBJCOPY) --only-keep-debug $@ $(subst specs/cells,build,$@.debug) 36 | $(OBJCOPY) --strip-debug --strip-all $@ 37 | 38 | build/secp256k1_data_info.h: build/dump_secp256k1_data 39 | $< 40 | 41 | build/dump_secp256k1_data: c/dump_secp256k1_data.c $(SECP256K1_SRC) 42 | mkdir -p build 43 | gcc $(CFLAGS) -o $@ $< 44 | 45 | $(SECP256K1_SRC): 46 | cd deps/secp256k1 && \ 47 | ./autogen.sh && \ 48 | CC=$(CC) LD=$(LD) ./configure --with-bignum=no --enable-ecmult-static-precomputation --enable-endomorphism --enable-module-recovery --host=$(TARGET) && \ 49 | make src/ecmult_static_pre_context.h src/ecmult_static_context.h 50 | 51 | generate-protocol: check-moleculec-version ${PROTOCOL_HEADER} 52 | 53 | check-moleculec-version: 54 | test "$$(${MOLC} --version | awk '{ print $$2 }' | tr -d ' ')" = ${MOLC_VERSION} 55 | 56 | ${PROTOCOL_HEADER}: ${PROTOCOL_SCHEMA} 57 | ${MOLC} --language c --schema-file $< > $@ 58 | 59 | ${PROTOCOL_SCHEMA}: 60 | curl -L -o $@ ${PROTOCOL_URL} 61 | 62 | install-tools: 63 | if [ ! -x "$$(command -v "${MOLC}")" ] \ 64 | || [ "$$(${MOLC} --version | awk '{ print $$2 }' | tr -d ' ')" != "${MOLC_VERSION}" ]; then \ 65 | cargo install --force --version "${MOLC_VERSION}" "${MOLC}"; \ 66 | fi 67 | 68 | publish: 69 | git diff --exit-code Cargo.toml 70 | sed -i.bak 's/.*git =/# &/' Cargo.toml 71 | cargo publish --allow-dirty 72 | git checkout Cargo.toml Cargo.lock 73 | rm -f Cargo.toml.bak 74 | 75 | package: 76 | git diff --exit-code Cargo.toml 77 | sed -i.bak 's/.*git =/# &/' Cargo.toml 78 | cargo package --allow-dirty 79 | git checkout Cargo.toml Cargo.lock 80 | rm -f Cargo.toml.bak 81 | 82 | package-clean: 83 | git checkout Cargo.toml Cargo.lock 84 | rm -rf Cargo.toml.bak target/package/ 85 | 86 | clean: 87 | rm -rf ${PROTOCOL_HEADER} ${PROTOCOL_SCHEMA} 88 | rm -rf specs/cells/ckb_cell_upgrade specs/cells/secp256k1_keccak256_sighash_all specs/cells/secp256k1_keccak256_sighash_all_acpl 89 | rm -rf build/secp256k1_data_info.h build/dump_secp256k1_data 90 | rm -rf specs/cells/secp256k1_data 91 | rm -rf build/*.debug 92 | cd deps/secp256k1 && [ -f "Makefile" ] && make clean 93 | cargo clean 94 | 95 | fmt: 96 | clang-format -i -style=Google $(wildcard c/*.h c/*.c) 97 | # git diff --exit-code $(wildcard c/*.h c/*.c) 98 | 99 | dist: clean all 100 | 101 | .PHONY: all all-via-docker dist clean package-clean package publish 102 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | ## pw-lock机制在CKB上的实现 2 | 3 | CKB官方的锁定脚本使用blake2b计算hash,使用secp256k1_ecdsa_recover进行签名验证。其与ETH生态的验签主要差别在于hash的计算方式,ETH使用的是keccak256来计算hash。 4 | 5 | 由于CKB脚本的灵活性,能支持各种自定义的脚本。 我们开发了一种能够验证ETH生态签名的锁定脚本,并将其命名为pw-lock。 6 | 7 | ## 快速开始 8 | 9 | ``` 10 | git submodule init 11 | git submodule update 12 | make install-tools 13 | make all-via-docker 14 | cargo test --all 15 | ``` 16 | 17 | ## 原理 18 | 19 | ### 官方交易签名基本逻辑: 20 | 21 | 1. 计算CKB交易hash tx_hash 22 | 23 | 2. 计算交易签名前hash = blake2b(tx_hash, input_witnesses, extra_witnesses) 24 | 25 | 3. 使用私钥进行签名signedWitness = ecsda_sign(hash, privateKey) 26 | 27 | 28 | ## pw-lock脚本实现 29 | 30 | 鉴于EIP712能够为用户展示更加直观和安全的信息,pw-lock优先支持了EIP712相关的签名方式signTypedData_v4。但是EIP712目前只有少数的几个ETH钱包能够完全支持,为了能够全面支持ETH生态,pw-lock同时也适配了当前绝大多数ETH钱包都能支持的personalSign签名方式。 31 | 32 | 因此,pw-lock脚本提供了两种签名的验证方式: personalSign和signTypedData_v4。 33 | 34 | ### 验证签名:eth_personalSign 35 | 36 | #### hash计算方式 37 | 同CKB交易官方计算hash方式(参考官方lock脚本[secp256k1_blake160_sighash_all.c](https://github.com/nervosnetwork/ckb-system-scripts/blob/master/c/secp256k1_blake160_sighash_all.c)),只是将blake2b替换成keccak256。 38 | 39 | #### 验签 40 | ```javascript 41 | // 以下为伪代码,实际代码为c语言编写 42 | const newHash = hashPersonalMessage(hash) 43 | 44 | /* 45 | hashPersonalMessage = function(message: Buffer): Buffer { 46 | const prefix = Buffer.from( 47 | `\u0019Ethereum Signed Message:\n${message.length.toString()}`, 48 | 'utf-8', 49 | ) 50 | return keccak(Buffer.concat([prefix, message])) 51 | } 52 | */ 53 | 54 | const pubkey = secp256k1_ecdsa_recover(signature, newHash) 55 | if (pubkey.slice(12, 32) === lock.args){ 56 | return 0; 57 | } 58 | ``` 59 | 60 | 1. 使用ECDSA_RECOVER算法从newHash和签名计算出32位pubkey。 61 | 2. 检测pubkey的后20位是否等于lock args(也就是ETH地址)。 62 | 63 | ### 验证签名:eth_signTypedData_v4 64 | 65 | #### hash计算方式 66 | 同CKB交易官方计算hash方式,只是将blake2b替换成keccak256。 67 | 68 | ```javascript 69 | // 以下为伪代码,实际代码为c语言编写 70 | const newHash = hashPersonalMessage(hash) 71 | const typedData = { 72 | domain: { 73 | chainId: 1, 74 | name: 'ckb.pw', 75 | verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', 76 | version: '1' 77 | }, 78 | 79 | message: { 80 | hash: 81 | '0x545529d4464064d8394c557afb06f489e7044a63984c6113385431d93dcffa1b', 82 | fee: '0.00100000CKB', 83 | 'input-sum': '100.00000000CKB', 84 | to: [ 85 | { 86 | address: 'ckb1qyqv4yga3pgw2h92hcnur7lepdfzmvg8wj7qwstnwm', 87 | amount: '100.00000000CKB' 88 | }, 89 | { 90 | address: 91 | 'ckb1qftyhqxwuxdzp5zk4rctscnrr6stjrmfjdx54v05q8t3ad3493m6mhcekrn0vk575h44ql9ry53z3gzhtc2exudxcyg', 92 | amount: '799.99800000CKB' 93 | } 94 | ] 95 | }, 96 | primaryType: 'CKBTransaction', 97 | types: { 98 | EIP712Domain: [ 99 | { name: 'name', type: 'string' }, 100 | { name: 'version', type: 'string' }, 101 | { name: 'chainId', type: 'uint256' }, 102 | { name: 'verifyingContract', type: 'address' } 103 | ], 104 | CKBTransaction: [ 105 | { name: 'hash', type: 'bytes32' }, 106 | { name: 'fee', type: 'string' }, 107 | { name: 'input-sum', type: 'string' }, 108 | { name: 'to', type: 'Output[]' } 109 | ], 110 | Output: [ 111 | { name: 'address', type: 'string' }, 112 | { name: 'amount', type: 'string' } 113 | ] 114 | } 115 | } 116 | 117 | typedData.message.hash = newHash 118 | typedData.message['input-sum'] = total_input_amount(tx) 119 | typedData.message.fee = total_input_amount(tx) - total_output_amount(tx) 120 | typedData.message.to = extractTxOutputsInfo(tx) 121 | 122 | ``` 123 | 根据CKB交易信息计算出input-sum/fee/to相关信息,并赋值给typedData的对应属性。 124 | 125 | #### 验签 126 | ```javascript 127 | // 以下为伪代码,实际代码为c语言编写 128 | 129 | const sigUtil = require('eth-sig-util') 130 | 131 | const typedHash = sigUtil.TypedDataUtils.sign(typedData) 132 | const pubkey = secp256k1_ecdsa_recover(signature, typedHash) 133 | if( pubkey.slice(12,32) === lock.args){ 134 | return 0; 135 | } 136 | ``` 137 | 138 | 1. 根据typedData计算出typedHash,使用ECDSA_RECOVER算法从typedHash和签名计算出32位pubkey。 139 | 2. 检测pubkey的后20位是否等于lock args(也就是ETH地址)。 140 | 141 | 142 | ### 支持Anyone-can-pay 143 | 144 | pw-lock 也集成了anyone-can-pay的特性,与官方发布的[anyone-can-pay](https://github.com/nervosnetwork/ckb-anyone-can-pay)一致 145 | 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pw-lock 2 | 3 | Forked from CKB's system scripts, and currently supports signature generated by personalSign and signTypedData from ethereum wallets. 4 | 5 | The CKB system script [**secp256k1_blake160_sighash_all**](https://github.com/nervosnetwork/ckb-system-scripts/blob/master/c/secp256k1_blake160_sighash_all.c) uses blake2b to calculate the hash, and secp256k1_ecdsa_recover for signature verification. The main difference between CKB and the Ethereum signature verification is the hash calculation method. Ethereum uses keccak256 instead of blake2b to calculate the hash. 6 | 7 | ## Quick Start 8 | 9 | ``` 10 | git submodule init 11 | git submodule update 12 | make install-tools 13 | make all-via-docker 14 | cargo test --all 15 | ``` 16 | 17 | ## Script Implementation 18 | 19 | In view of the fact that [EIP712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) can show users more intuitive and safe information, pw-lock preferentially supports EIP712 related signature method [eth_signTypedData_v4](https://github.com/MetaMask/eth-sig-util/blob/11bfc0345f5fcd91b23e721c17dd32df08465d9c/index.ts#L493). However, currently only a few ETH wallets can fully support EIP712. In order to fully support the Ethereum ecosystem, pw-lock also adapts to the [personalSign](https://github.com/MetaMask/eth-sig-util/blob/11bfc0345f5fcd91b23e721c17dd32df08465d9c/index.ts#L299) signature currently supported by most Ethereum wallets. 20 | 21 | pw-lock script provides **two** signature verification methods: **eth_personalSign** and **eth_signTypedData_v4**. 22 | 23 | ### eth_personalSign 24 | 25 | #### hash calculation 26 | The same hash calculation process is the same as the CKB system lock script [secp256k1_blake160_sighash_all.c] (https://github.com/nervosnetwork/ckb-system-scripts/blob/master/c/secp256k1_blake160_sighash_all.c), but replace blake2b into keccak256. 27 | 28 | #### signature verification 29 | ```javascript 30 | // pseudo code, the actual code is written in C language 31 | const newHash = hashPersonalMessage(hash) 32 | 33 | /* 34 | hashPersonalMessage = function(message: Buffer): Buffer { 35 | const prefix = Buffer.from( 36 | `\u0019Ethereum Signed Message:\n${message.length.toString()}`, 37 | 'utf-8', 38 | ) 39 | return keccak(Buffer.concat([prefix, message])) 40 | } 41 | */ 42 | 43 | const pubkey = secp256k1_ecdsa_recover(signature, newHash) 44 | if (pubkey.slice(12, 32) === lock.args){ 45 | return 0; 46 | } 47 | ``` 48 | 1. Use the ECDSA_RECOVER algorithm to calculate the 32-byte pubkey from the personalHash and signature. 49 | 2. Check if the last 20 bytes of pubkey are equal to lock args (that is, Ethereum address). 50 | 51 | 52 | 53 | ### eth_signTypedData_v4 54 | 55 | #### hash calculation 56 | The hash calculation process is the same as the CKB system lock script, except that blake2b is replaced by keccak256. 57 | 58 | #### signature verification 59 | 60 | ```javascript 61 | // pseudo code, the actual code is written in C language 62 | const newHash = hashPersonalMessage(hash) 63 | const typedData = { 64 | domain: { 65 | chainId: 1, 66 | name: 'ckb.pw', 67 | verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', 68 | version: '1' 69 | }, 70 | 71 | message: { 72 | hash: 73 | '0x545529d4464064d8394c557afb06f489e7044a63984c6113385431d93dcffa1b', 74 | fee: '0.00100000CKB', 75 | 'input-sum': '100.00000000CKB', 76 | to: [ 77 | { 78 | address: 'ckb1qyqv4yga3pgw2h92hcnur7lepdfzmvg8wj7qwstnwm', 79 | amount: '100.00000000CKB' 80 | }, 81 | { 82 | address: 83 | 'ckb1qftyhqxwuxdzp5zk4rctscnrr6stjrmfjdx54v05q8t3ad3493m6mhcekrn0vk575h44ql9ry53z3gzhtc2exudxcyg', 84 | amount: '799.99800000CKB' 85 | } 86 | ] 87 | }, 88 | primaryType: 'CKBTransaction', 89 | types: { 90 | EIP712Domain: [ 91 | { name: 'name', type: 'string' }, 92 | { name: 'version', type: 'string' }, 93 | { name: 'chainId', type: 'uint256' }, 94 | { name: 'verifyingContract', type: 'address' } 95 | ], 96 | CKBTransaction: [ 97 | { name: 'hash', type: 'bytes32' }, 98 | { name: 'fee', type: 'string' }, 99 | { name: 'input-sum', type: 'string' }, 100 | { name: 'to', type: 'Output[]' } 101 | ], 102 | Output: [ 103 | { name: 'address', type: 'string' }, 104 | { name: 'amount', type: 'string' } 105 | ] 106 | } 107 | } 108 | 109 | typedData.message.hash = newHash 110 | typedData.message['input-sum'] = total_input_amount(tx) 111 | typedData.message.fee = total_input_amount(tx) - total_output_amount(tx) 112 | typedData.message.to = extractTxOutputsInfo(tx) 113 | 114 | ``` 115 | According to the CKB transaction information, input-sum / fee / to related informations are calculated and assigned to the corresponding attribute of typedData. 116 | 117 | #### signature verification 118 | ```javascript 119 | // pseudo code, the actual code is written in C language 120 | 121 | const sigUtil = require('eth-sig-util') 122 | 123 | const typedHash = sigUtil.TypedDataUtils.sign(typedData) 124 | const pubkey = secp256k1_ecdsa_recover(signature, typedHash) 125 | if( pubkey.slice(12,32) === lock.args){ 126 | return 0; 127 | } 128 | ``` 129 | 130 | 1. Calculate typedHash by typedData, and use ECDSA_RECOVER algorithm to calculate 32-byte pubkey from typedHash and signature. 131 | 2. Check if the last 20 bytes of pubkey are equal to lock args (that is, Ethereum address). 132 | 133 | 134 | ### Anyone-can-pay Support 135 | 136 | pw-lock also integrates the features of anyone-can-pay, which is consistent with the official [anyone-can-pay](https://github.com/nervosnetwork/ckb-anyone-can-pay) -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | pub use blake2b_rs::{Blake2b, Blake2bBuilder}; 2 | use includedir_codegen::Compression; 3 | 4 | use std::{ 5 | env, 6 | fs::File, 7 | io::{BufWriter, Read, Write}, 8 | path::Path, 9 | }; 10 | 11 | const PATH_PREFIX: &str = "specs/cells/"; 12 | const BUF_SIZE: usize = 8 * 1024; 13 | const CKB_HASH_PERSONALIZATION: &[u8] = b"ckb-default-hash"; 14 | 15 | const BINARIES: &[(&str, &str)] = &[ 16 | ( 17 | "secp256k1_data", 18 | "9799bee251b975b82c45a02154ce28cec89c5853ecc14d12b7b8cccfc19e0af4", 19 | ), 20 | ( 21 | "secp256k1_keccak256_sighash_all", 22 | "e35b54c9bb6355e3fe694c9900bcb34904daef128b71a7e58b514d35e1cf2f8e", 23 | ), 24 | ( 25 | "secp256k1_keccak256_sighash_all_acpl", 26 | "853d8a15a968193351040aa602a66403d6d47670f9dbfd830f8ec4ae9586046d", 27 | ), 28 | ]; 29 | 30 | fn main() { 31 | let mut bundled = includedir_codegen::start("BUNDLED_CELL"); 32 | 33 | let out_path = Path::new(&env::var("OUT_DIR").unwrap()).join("code_hashes.rs"); 34 | let mut out_file = BufWriter::new(File::create(&out_path).expect("create code_hashes.rs")); 35 | 36 | let mut errors = Vec::new(); 37 | 38 | for (name, expected_hash) in BINARIES { 39 | let path = format!("{}{}", PATH_PREFIX, name); 40 | 41 | let mut buf = [0u8; BUF_SIZE]; 42 | bundled 43 | .add_file(&path, Compression::Gzip) 44 | .expect("add files to resource bundle"); 45 | 46 | // build hash 47 | let mut blake2b = new_blake2b(); 48 | let mut fd = File::open(&path).expect("open file"); 49 | loop { 50 | let read_bytes = fd.read(&mut buf).expect("read file"); 51 | if read_bytes > 0 { 52 | blake2b.update(&buf[..read_bytes]); 53 | } else { 54 | break; 55 | } 56 | } 57 | 58 | let mut hash = [0u8; 32]; 59 | blake2b.finalize(&mut hash); 60 | 61 | let actual_hash = faster_hex::hex_string(&hash).unwrap(); 62 | if expected_hash != &actual_hash { 63 | errors.push((name, expected_hash, actual_hash)); 64 | continue; 65 | } 66 | 67 | write!( 68 | &mut out_file, 69 | "pub const {}: [u8; 32] = {:?};\n", 70 | format!("CODE_HASH_{}", name.to_uppercase()), 71 | hash 72 | ) 73 | .expect("write to code_hashes.rs"); 74 | } 75 | 76 | // if !errors.is_empty() { 77 | // for (name, expected, actual) in errors.into_iter() { 78 | // eprintln!("{}: expect {}, actual {}", name, expected, actual); 79 | // } 80 | // panic!("not all hashes are right"); 81 | // } 82 | 83 | bundled.build("bundled.rs").expect("build resource bundle"); 84 | } 85 | 86 | pub fn new_blake2b() -> Blake2b { 87 | Blake2bBuilder::new(32) 88 | .personal(CKB_HASH_PERSONALIZATION) 89 | .build() 90 | } 91 | -------------------------------------------------------------------------------- /c/anyone_can_pay_lock.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "ckb_syscalls.h" 4 | #include "overflow_add.h" 5 | #include "protocol.h" 6 | 7 | #define BLAKE2B_BLOCK_SIZE 32 8 | #define SCRIPT_SIZE 32768 9 | #define CKB_LEN 8 10 | #define UDT_LEN 16 11 | #define MAX_WITNESS_SIZE 32768 12 | #define MAX_TYPE_HASH 256 13 | 14 | typedef struct { 15 | int is_ckb_only; 16 | unsigned char type_hash[BLAKE2B_BLOCK_SIZE]; 17 | uint64_t ckb_amount; 18 | uint128_t udt_amount; 19 | uint32_t output_cnt; 20 | } InputWallet; 21 | 22 | int check_payment_unlock(uint64_t min_ckb_amount, uint128_t min_udt_amount) { 23 | unsigned char lock_hash[BLAKE2B_BLOCK_SIZE]; 24 | InputWallet input_wallets[MAX_TYPE_HASH]; 25 | uint64_t len = BLAKE2B_BLOCK_SIZE; 26 | /* load wallet lock hash */ 27 | int ret = ckb_load_script_hash(lock_hash, &len, 0); 28 | if (ret != CKB_SUCCESS) { 29 | return ERROR_SYSCALL; 30 | } 31 | if (len > BLAKE2B_BLOCK_SIZE) { 32 | return ERROR_SCRIPT_TOO_LONG; 33 | } 34 | 35 | /* iterate inputs and find input wallet cell */ 36 | int i = 0; 37 | len = BLAKE2B_BLOCK_SIZE; 38 | while (1) { 39 | if (i >= MAX_TYPE_HASH) { 40 | return ERROR_TOO_MUCH_TYPE_HASH_INPUTS; 41 | } 42 | 43 | ret = ckb_checked_load_cell_by_field(input_wallets[i].type_hash, &len, 0, i, 44 | CKB_SOURCE_GROUP_INPUT, 45 | CKB_CELL_FIELD_TYPE_HASH); 46 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 47 | break; 48 | } 49 | 50 | if (ret == CKB_SUCCESS) { 51 | if (len != BLAKE2B_BLOCK_SIZE) { 52 | return ERROR_ENCODING; 53 | } 54 | } else if (ret != CKB_ITEM_MISSING) { 55 | return ERROR_SYSCALL; 56 | } 57 | 58 | input_wallets[i].is_ckb_only = ret == CKB_ITEM_MISSING; 59 | 60 | /* load amount */ 61 | len = CKB_LEN; 62 | ret = ckb_checked_load_cell_by_field( 63 | (uint8_t *)&input_wallets[i].ckb_amount, &len, 0, i, 64 | CKB_SOURCE_GROUP_INPUT, CKB_CELL_FIELD_CAPACITY); 65 | if (ret != CKB_SUCCESS) { 66 | return ERROR_SYSCALL; 67 | } 68 | if (len != CKB_LEN) { 69 | return ERROR_ENCODING; 70 | } 71 | len = UDT_LEN; 72 | ret = ckb_load_cell_data((uint8_t *)&input_wallets[i].udt_amount, &len, 0, 73 | i, CKB_SOURCE_GROUP_INPUT); 74 | if (ret != CKB_ITEM_MISSING && ret != CKB_SUCCESS) { 75 | return ERROR_SYSCALL; 76 | } 77 | 78 | if (input_wallets[i].is_ckb_only) { 79 | /* ckb only wallet should has no data */ 80 | if (len != 0) { 81 | return ERROR_ENCODING; 82 | } 83 | } else { 84 | if (len < UDT_LEN) { 85 | return ERROR_ENCODING; 86 | } 87 | } 88 | 89 | i++; 90 | } 91 | 92 | int input_wallets_cnt = i; 93 | 94 | /* iterate outputs wallet cell */ 95 | i = 0; 96 | while (1) { 97 | uint8_t output_lock_hash[BLAKE2B_BLOCK_SIZE]; 98 | uint8_t output_type_hash[BLAKE2B_BLOCK_SIZE]; 99 | uint64_t len = BLAKE2B_BLOCK_SIZE; 100 | /* check lock hash */ 101 | ret = ckb_checked_load_cell_by_field(output_lock_hash, &len, 0, i, 102 | CKB_SOURCE_OUTPUT, 103 | CKB_CELL_FIELD_LOCK_HASH); 104 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 105 | break; 106 | } 107 | if (ret != CKB_SUCCESS) { 108 | return ret; 109 | } 110 | if (len != BLAKE2B_BLOCK_SIZE) { 111 | return ERROR_ENCODING; 112 | } 113 | int has_same_lock = 114 | memcmp(output_lock_hash, lock_hash, BLAKE2B_BLOCK_SIZE) == 0; 115 | if (!has_same_lock) { 116 | i++; 117 | continue; 118 | } 119 | /* load type hash */ 120 | len = BLAKE2B_BLOCK_SIZE; 121 | ret = ckb_checked_load_cell_by_field(output_type_hash, &len, 0, i, 122 | CKB_SOURCE_OUTPUT, 123 | CKB_CELL_FIELD_TYPE_HASH); 124 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 125 | break; 126 | } 127 | if (ret == CKB_SUCCESS) { 128 | if (len != BLAKE2B_BLOCK_SIZE) { 129 | return ERROR_ENCODING; 130 | } 131 | } else if (ret != CKB_ITEM_MISSING) { 132 | return ERROR_SYSCALL; 133 | } 134 | int is_ckb_only = ret == CKB_ITEM_MISSING; 135 | 136 | /* load amount */ 137 | uint64_t ckb_amount; 138 | uint128_t udt_amount; 139 | len = CKB_LEN; 140 | ret = ckb_checked_load_cell_by_field((uint8_t *)&ckb_amount, &len, 0, i, 141 | CKB_SOURCE_OUTPUT, 142 | CKB_CELL_FIELD_CAPACITY); 143 | if (ret != CKB_SUCCESS) { 144 | return ERROR_SYSCALL; 145 | } 146 | if (len != CKB_LEN) { 147 | return ERROR_ENCODING; 148 | } 149 | len = UDT_LEN; 150 | ret = ckb_load_cell_data((uint8_t *)&udt_amount, &len, 0, i, 151 | CKB_SOURCE_OUTPUT); 152 | if (ret != CKB_ITEM_MISSING && ret != CKB_SUCCESS) { 153 | return ERROR_SYSCALL; 154 | } 155 | 156 | if (is_ckb_only) { 157 | /* ckb only wallet should has no data */ 158 | if (len != 0) { 159 | return ERROR_ENCODING; 160 | } 161 | } else { 162 | if (len < UDT_LEN) { 163 | return ERROR_ENCODING; 164 | } 165 | } 166 | 167 | /* find input wallet which has same type hash */ 168 | int found_inputs = 0; 169 | for (int j = 0; j < input_wallets_cnt; j++) { 170 | int has_same_type = 0; 171 | /* check type hash */ 172 | if (is_ckb_only) { 173 | has_same_type = input_wallets[j].is_ckb_only; 174 | } else { 175 | has_same_type = memcmp(output_type_hash, input_wallets[j].type_hash, 176 | BLAKE2B_BLOCK_SIZE) == 0; 177 | } 178 | if (!has_same_type) { 179 | continue; 180 | } 181 | /* compare amount */ 182 | uint64_t min_output_ckb_amount; 183 | uint128_t min_output_udt_amount; 184 | int overflow; 185 | overflow = uint64_overflow_add( 186 | &min_output_ckb_amount, input_wallets[j].ckb_amount, min_ckb_amount); 187 | int meet_ckb_cond = !overflow && ckb_amount >= min_output_ckb_amount; 188 | overflow = uint128_overflow_add( 189 | &min_output_udt_amount, input_wallets[j].udt_amount, min_udt_amount); 190 | int meet_udt_cond = !overflow && udt_amount >= min_output_udt_amount; 191 | 192 | /* fail if can't meet both conditions */ 193 | if (!(meet_ckb_cond || meet_udt_cond)) { 194 | return ERROR_OUTPUT_AMOUNT_NOT_ENOUGH; 195 | } 196 | /* output coins must meet condition, or remain the old amount */ 197 | if ((!meet_ckb_cond && ckb_amount != input_wallets[j].ckb_amount) || 198 | (!meet_udt_cond && udt_amount != input_wallets[j].udt_amount)) { 199 | return ERROR_OUTPUT_AMOUNT_NOT_ENOUGH; 200 | } 201 | 202 | /* increase counter */ 203 | found_inputs++; 204 | input_wallets[j].output_cnt += 1; 205 | if (found_inputs > 1) { 206 | return ERROR_DUPLICATED_INPUTS; 207 | } 208 | if (input_wallets[j].output_cnt > 1) { 209 | return ERROR_DUPLICATED_OUTPUTS; 210 | } 211 | } 212 | 213 | /* one output should pair with one input */ 214 | if (found_inputs == 0) { 215 | return ERROR_NO_PAIR; 216 | } else if (found_inputs > 1) { 217 | return ERROR_DUPLICATED_INPUTS; 218 | } 219 | 220 | i++; 221 | } 222 | 223 | /* check inputs wallet, one input should pair with one output */ 224 | for (int j = 0; j < input_wallets_cnt; j++) { 225 | if (input_wallets[j].output_cnt == 0) { 226 | return ERROR_NO_PAIR; 227 | } else if (input_wallets[j].output_cnt > 1) { 228 | return ERROR_DUPLICATED_OUTPUTS; 229 | } 230 | } 231 | 232 | return CKB_SUCCESS; 233 | } 234 | -------------------------------------------------------------------------------- /c/bech32.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017 Pieter Wuille 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | /** Encode a Bech32 string 26 | * 27 | * Out: output: Pointer to a buffer of size strlen(hrp) + data_len + 8 that 28 | * will be updated to contain the null-terminated Bech32 string. 29 | * In: hrp : Pointer to the null-terminated human readable part. 30 | * data : Pointer to an array of 5-bit values. 31 | * data_len: Length of the data array. 32 | * Returns 1 if successful. 33 | */ 34 | int bech32_encode(char *output, const char *hrp, const uint8_t *data, 35 | size_t data_len); 36 | 37 | /** Decode a Bech32 string 38 | * 39 | * Out: hrp: Pointer to a buffer of size strlen(input) - 6. Will be 40 | * updated to contain the null-terminated human readable part. 41 | * data: Pointer to a buffer of size strlen(input) - 8 that will 42 | * hold the encoded 5-bit data values. 43 | * data_len: Pointer to a size_t that will be updated to be the number 44 | * of entries in data. 45 | * In: input: Pointer to a null-terminated Bech32 string. 46 | * Returns 1 if succesful. 47 | */ 48 | int bech32_decode(char *hrp, uint8_t *data, size_t *data_len, 49 | const char *input); 50 | 51 | uint32_t bech32_polymod_step(uint32_t pre) { 52 | uint8_t b = pre >> 25; 53 | return ((pre & 0x1FFFFFF) << 5) ^ (-((b >> 0) & 1) & 0x3b6a57b2UL) ^ 54 | (-((b >> 1) & 1) & 0x26508e6dUL) ^ (-((b >> 2) & 1) & 0x1ea119faUL) ^ 55 | (-((b >> 3) & 1) & 0x3d4233ddUL) ^ (-((b >> 4) & 1) & 0x2a1462b3UL); 56 | } 57 | 58 | static const char *charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; 59 | 60 | static const int8_t charset_rev[128] = { 61 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 63 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 64 | 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 65 | 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, 66 | -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 67 | 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; 68 | 69 | int bech32_encode(char *output, const char *hrp, const uint8_t *data, 70 | size_t data_len) { 71 | uint32_t chk = 1; 72 | size_t i = 0; 73 | while (hrp[i] != 0) { 74 | int ch = hrp[i]; 75 | if (ch < 33 || ch > 126) { 76 | return 0; 77 | } 78 | 79 | if (ch >= 'A' && ch <= 'Z') return 0; 80 | chk = bech32_polymod_step(chk) ^ (ch >> 5); 81 | ++i; 82 | } 83 | // if (i + 7 + data_len > 90) return 0; 84 | chk = bech32_polymod_step(chk); 85 | while (*hrp != 0) { 86 | chk = bech32_polymod_step(chk) ^ (*hrp & 0x1f); 87 | *(output++) = *(hrp++); 88 | } 89 | *(output++) = '1'; 90 | for (i = 0; i < data_len; ++i) { 91 | if (*data >> 5) return 0; 92 | chk = bech32_polymod_step(chk) ^ (*data); 93 | *(output++) = charset[*(data++)]; 94 | } 95 | for (i = 0; i < 6; ++i) { 96 | chk = bech32_polymod_step(chk); 97 | } 98 | chk ^= 1; 99 | for (i = 0; i < 6; ++i) { 100 | *(output++) = charset[(chk >> ((5 - i) * 5)) & 0x1f]; 101 | } 102 | *output = 0; 103 | return 1; 104 | } 105 | 106 | int bech32_decode(char *hrp, uint8_t *data, size_t *data_len, 107 | const char *input) { 108 | uint32_t chk = 1; 109 | size_t i; 110 | size_t input_len = strlen(input); 111 | size_t hrp_len; 112 | int have_lower = 0, have_upper = 0; 113 | if (input_len < 8 || input_len > 90) { 114 | return 0; 115 | } 116 | *data_len = 0; 117 | while (*data_len < input_len && input[(input_len - 1) - *data_len] != '1') { 118 | ++(*data_len); 119 | } 120 | hrp_len = input_len - (1 + *data_len); 121 | if (1 + *data_len >= input_len || *data_len < 6) { 122 | return 0; 123 | } 124 | *(data_len) -= 6; 125 | for (i = 0; i < hrp_len; ++i) { 126 | int ch = input[i]; 127 | if (ch < 33 || ch > 126) { 128 | return 0; 129 | } 130 | if (ch >= 'a' && ch <= 'z') { 131 | have_lower = 1; 132 | } else if (ch >= 'A' && ch <= 'Z') { 133 | have_upper = 1; 134 | ch = (ch - 'A') + 'a'; 135 | } 136 | hrp[i] = ch; 137 | chk = bech32_polymod_step(chk) ^ (ch >> 5); 138 | } 139 | hrp[i] = 0; 140 | chk = bech32_polymod_step(chk); 141 | for (i = 0; i < hrp_len; ++i) { 142 | chk = bech32_polymod_step(chk) ^ (input[i] & 0x1f); 143 | } 144 | ++i; 145 | while (i < input_len) { 146 | int v = (input[i] & 0x80) ? -1 : charset_rev[(int)input[i]]; 147 | if (input[i] >= 'a' && input[i] <= 'z') have_lower = 1; 148 | if (input[i] >= 'A' && input[i] <= 'Z') have_upper = 1; 149 | if (v == -1) { 150 | return 0; 151 | } 152 | chk = bech32_polymod_step(chk) ^ v; 153 | if (i + 6 < input_len) { 154 | data[i - (1 + hrp_len)] = v; 155 | } 156 | ++i; 157 | } 158 | if (have_lower && have_upper) { 159 | return 0; 160 | } 161 | return chk == 1; 162 | } 163 | 164 | static int convert_bits(uint8_t *out, size_t *outlen, int outbits, 165 | const uint8_t *in, size_t inlen, int inbits, int pad) { 166 | uint32_t val = 0; 167 | int bits = 0; 168 | uint32_t maxv = (((uint32_t)1) << outbits) - 1; 169 | while (inlen--) { 170 | val = (val << inbits) | *(in++); 171 | bits += inbits; 172 | while (bits >= outbits) { 173 | bits -= outbits; 174 | out[(*outlen)++] = (val >> bits) & maxv; 175 | } 176 | } 177 | if (pad) { 178 | if (bits) { 179 | out[(*outlen)++] = (val << (outbits - bits)) & maxv; 180 | } 181 | } else if (((val << (outbits - bits)) & maxv) || bits >= inbits) { 182 | return 0; 183 | } 184 | return 1; 185 | } 186 | -------------------------------------------------------------------------------- /c/blake2b.h: -------------------------------------------------------------------------------- 1 | /* 2 | BLAKE2 reference source code package - reference C implementations 3 | 4 | Copyright 2012, Samuel Neves . You may use this under the 5 | terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at 6 | your option. The terms of these licenses can be found at: 7 | 8 | - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 9 | - OpenSSL license : https://www.openssl.org/source/license.html 10 | - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | More information about the BLAKE2 hash function can be found at 13 | https://blake2.net. 14 | */ 15 | 16 | // blake2.h 17 | #ifndef BLAKE2_H 18 | #define BLAKE2_H 19 | 20 | #include 21 | #include 22 | 23 | #if defined(_MSC_VER) 24 | #define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) 25 | #else 26 | #define BLAKE2_PACKED(x) x __attribute__((packed)) 27 | #endif 28 | 29 | #if defined(__cplusplus) 30 | extern "C" { 31 | #endif 32 | 33 | enum blake2b_constant { 34 | BLAKE2B_BLOCKBYTES = 128, 35 | BLAKE2B_OUTBYTES = 64, 36 | BLAKE2B_KEYBYTES = 64, 37 | BLAKE2B_SALTBYTES = 16, 38 | BLAKE2B_PERSONALBYTES = 16 39 | }; 40 | 41 | typedef struct blake2b_state__ { 42 | uint64_t h[8]; 43 | uint64_t t[2]; 44 | uint64_t f[2]; 45 | uint8_t buf[BLAKE2B_BLOCKBYTES]; 46 | size_t buflen; 47 | size_t outlen; 48 | uint8_t last_node; 49 | } blake2b_state; 50 | 51 | BLAKE2_PACKED(struct blake2b_param__ { 52 | uint8_t digest_length; /* 1 */ 53 | uint8_t key_length; /* 2 */ 54 | uint8_t fanout; /* 3 */ 55 | uint8_t depth; /* 4 */ 56 | uint32_t leaf_length; /* 8 */ 57 | uint32_t node_offset; /* 12 */ 58 | uint32_t xof_length; /* 16 */ 59 | uint8_t node_depth; /* 17 */ 60 | uint8_t inner_length; /* 18 */ 61 | uint8_t reserved[14]; /* 32 */ 62 | uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ 63 | uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ 64 | }); 65 | 66 | typedef struct blake2b_param__ blake2b_param; 67 | 68 | /* Padded structs result in a compile-time error */ 69 | enum { BLAKE2_DUMMY_2 = 1 / (sizeof(blake2b_param) == BLAKE2B_OUTBYTES) }; 70 | 71 | /* Streaming API */ 72 | int blake2b_init(blake2b_state *S, size_t outlen); 73 | int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, 74 | size_t keylen); 75 | int blake2b_init_param(blake2b_state *S, const blake2b_param *P); 76 | int blake2b_update(blake2b_state *S, const void *in, size_t inlen); 77 | int blake2b_final(blake2b_state *S, void *out, size_t outlen); 78 | 79 | /* Simple API */ 80 | int blake2b(void *out, size_t outlen, const void *in, size_t inlen, 81 | const void *key, size_t keylen); 82 | 83 | /* This is simply an alias for blake2b */ 84 | int blake2(void *out, size_t outlen, const void *in, size_t inlen, 85 | const void *key, size_t keylen); 86 | 87 | #if defined(__cplusplus) 88 | } 89 | #endif 90 | 91 | #endif 92 | 93 | // blake2-impl.h 94 | #ifndef BLAKE2_IMPL_H 95 | #define BLAKE2_IMPL_H 96 | 97 | #include 98 | #include 99 | 100 | #if !defined(__cplusplus) && \ 101 | (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) 102 | #if defined(_MSC_VER) 103 | #define BLAKE2_INLINE __inline 104 | #elif defined(__GNUC__) 105 | #define BLAKE2_INLINE __inline__ 106 | #else 107 | #define BLAKE2_INLINE 108 | #endif 109 | #else 110 | #define BLAKE2_INLINE inline 111 | #endif 112 | 113 | static BLAKE2_INLINE uint64_t load64(const void *src) { 114 | #if defined(NATIVE_LITTLE_ENDIAN) 115 | uint64_t w; 116 | memcpy(&w, src, sizeof w); 117 | return w; 118 | #else 119 | const uint8_t *p = (const uint8_t *)src; 120 | return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | 121 | ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | 122 | ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40) | 123 | ((uint64_t)(p[6]) << 48) | ((uint64_t)(p[7]) << 56); 124 | #endif 125 | } 126 | 127 | static BLAKE2_INLINE void store32(void *dst, uint32_t w) { 128 | #if defined(NATIVE_LITTLE_ENDIAN) 129 | memcpy(dst, &w, sizeof w); 130 | #else 131 | uint8_t *p = (uint8_t *)dst; 132 | p[0] = (uint8_t)(w >> 0); 133 | p[1] = (uint8_t)(w >> 8); 134 | p[2] = (uint8_t)(w >> 16); 135 | p[3] = (uint8_t)(w >> 24); 136 | #endif 137 | } 138 | 139 | static BLAKE2_INLINE void store64(void *dst, uint64_t w) { 140 | #if defined(NATIVE_LITTLE_ENDIAN) 141 | memcpy(dst, &w, sizeof w); 142 | #else 143 | uint8_t *p = (uint8_t *)dst; 144 | p[0] = (uint8_t)(w >> 0); 145 | p[1] = (uint8_t)(w >> 8); 146 | p[2] = (uint8_t)(w >> 16); 147 | p[3] = (uint8_t)(w >> 24); 148 | p[4] = (uint8_t)(w >> 32); 149 | p[5] = (uint8_t)(w >> 40); 150 | p[6] = (uint8_t)(w >> 48); 151 | p[7] = (uint8_t)(w >> 56); 152 | #endif 153 | } 154 | 155 | static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) { 156 | return (w >> c) | (w << (64 - c)); 157 | } 158 | 159 | /* prevents compiler optimizing out memset() */ 160 | static BLAKE2_INLINE void secure_zero_memory(void *v, size_t n) { 161 | static void *(*const volatile memset_v)(void *, int, size_t) = &memset; 162 | memset_v(v, 0, n); 163 | } 164 | 165 | #endif 166 | 167 | // blake2b-ref.c 168 | #include 169 | #include 170 | #include 171 | 172 | static const uint64_t blake2b_IV[8] = { 173 | 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 174 | 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 175 | 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL}; 176 | 177 | static const uint8_t blake2b_sigma[12][16] = { 178 | {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 179 | {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, 180 | {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, 181 | {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, 182 | {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, 183 | {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, 184 | {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, 185 | {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, 186 | {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, 187 | {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, 188 | {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 189 | {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}}; 190 | 191 | static void blake2b_set_lastnode(blake2b_state *S) { S->f[1] = (uint64_t)-1; } 192 | 193 | /* Some helper functions, not necessarily useful */ 194 | static int blake2b_is_lastblock(const blake2b_state *S) { return S->f[0] != 0; } 195 | 196 | static void blake2b_set_lastblock(blake2b_state *S) { 197 | if (S->last_node) blake2b_set_lastnode(S); 198 | 199 | S->f[0] = (uint64_t)-1; 200 | } 201 | 202 | static void blake2b_increment_counter(blake2b_state *S, const uint64_t inc) { 203 | S->t[0] += inc; 204 | S->t[1] += (S->t[0] < inc); 205 | } 206 | 207 | static void blake2b_init0(blake2b_state *S) { 208 | size_t i; 209 | memset(S, 0, sizeof(blake2b_state)); 210 | 211 | for (i = 0; i < 8; ++i) S->h[i] = blake2b_IV[i]; 212 | } 213 | 214 | /* init xors IV with input parameter block */ 215 | int blake2b_init_param(blake2b_state *S, const blake2b_param *P) { 216 | const uint8_t *p = (const uint8_t *)(P); 217 | size_t i; 218 | 219 | blake2b_init0(S); 220 | 221 | /* IV XOR ParamBlock */ 222 | for (i = 0; i < 8; ++i) S->h[i] ^= load64(p + sizeof(S->h[i]) * i); 223 | 224 | S->outlen = P->digest_length; 225 | return 0; 226 | } 227 | 228 | const char *DEFAULT_PERSONAL = "ckb-default-hash"; 229 | int blake2b_init(blake2b_state *S, size_t outlen) { 230 | blake2b_param P[1]; 231 | 232 | if ((!outlen) || (outlen > BLAKE2B_OUTBYTES)) return -1; 233 | 234 | P->digest_length = (uint8_t)outlen; 235 | P->key_length = 0; 236 | P->fanout = 1; 237 | P->depth = 1; 238 | store32(&P->leaf_length, 0); 239 | store32(&P->node_offset, 0); 240 | store32(&P->xof_length, 0); 241 | P->node_depth = 0; 242 | P->inner_length = 0; 243 | memset(P->reserved, 0, sizeof(P->reserved)); 244 | memset(P->salt, 0, sizeof(P->salt)); 245 | memset(P->personal, 0, sizeof(P->personal)); 246 | for (int i = 0; i < BLAKE2B_PERSONALBYTES; ++i) { 247 | (P->personal)[i] = DEFAULT_PERSONAL[i]; 248 | } 249 | return blake2b_init_param(S, P); 250 | } 251 | 252 | int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, 253 | size_t keylen) { 254 | blake2b_param P[1]; 255 | 256 | if ((!outlen) || (outlen > BLAKE2B_OUTBYTES)) return -1; 257 | 258 | if (!key || !keylen || keylen > BLAKE2B_KEYBYTES) return -1; 259 | 260 | P->digest_length = (uint8_t)outlen; 261 | P->key_length = (uint8_t)keylen; 262 | P->fanout = 1; 263 | P->depth = 1; 264 | store32(&P->leaf_length, 0); 265 | store32(&P->node_offset, 0); 266 | store32(&P->xof_length, 0); 267 | P->node_depth = 0; 268 | P->inner_length = 0; 269 | memset(P->reserved, 0, sizeof(P->reserved)); 270 | memset(P->salt, 0, sizeof(P->salt)); 271 | memset(P->personal, 0, sizeof(P->personal)); 272 | 273 | if (blake2b_init_param(S, P) < 0) return -1; 274 | 275 | { 276 | uint8_t block[BLAKE2B_BLOCKBYTES]; 277 | memset(block, 0, BLAKE2B_BLOCKBYTES); 278 | memcpy(block, key, keylen); 279 | blake2b_update(S, block, BLAKE2B_BLOCKBYTES); 280 | secure_zero_memory(block, BLAKE2B_BLOCKBYTES); /* Burn the key from stack */ 281 | } 282 | return 0; 283 | } 284 | 285 | #define G(r, i, a, b, c, d) \ 286 | do { \ 287 | a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \ 288 | d = rotr64(d ^ a, 32); \ 289 | c = c + d; \ 290 | b = rotr64(b ^ c, 24); \ 291 | a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \ 292 | d = rotr64(d ^ a, 16); \ 293 | c = c + d; \ 294 | b = rotr64(b ^ c, 63); \ 295 | } while (0) 296 | 297 | #define ROUND(r) \ 298 | do { \ 299 | G(r, 0, v[0], v[4], v[8], v[12]); \ 300 | G(r, 1, v[1], v[5], v[9], v[13]); \ 301 | G(r, 2, v[2], v[6], v[10], v[14]); \ 302 | G(r, 3, v[3], v[7], v[11], v[15]); \ 303 | G(r, 4, v[0], v[5], v[10], v[15]); \ 304 | G(r, 5, v[1], v[6], v[11], v[12]); \ 305 | G(r, 6, v[2], v[7], v[8], v[13]); \ 306 | G(r, 7, v[3], v[4], v[9], v[14]); \ 307 | } while (0) 308 | 309 | static void blake2b_compress(blake2b_state *S, 310 | const uint8_t block[BLAKE2B_BLOCKBYTES]) { 311 | uint64_t m[16]; 312 | uint64_t v[16]; 313 | size_t i; 314 | 315 | for (i = 0; i < 16; ++i) { 316 | m[i] = load64(block + i * sizeof(m[i])); 317 | } 318 | 319 | for (i = 0; i < 8; ++i) { 320 | v[i] = S->h[i]; 321 | } 322 | 323 | v[8] = blake2b_IV[0]; 324 | v[9] = blake2b_IV[1]; 325 | v[10] = blake2b_IV[2]; 326 | v[11] = blake2b_IV[3]; 327 | v[12] = blake2b_IV[4] ^ S->t[0]; 328 | v[13] = blake2b_IV[5] ^ S->t[1]; 329 | v[14] = blake2b_IV[6] ^ S->f[0]; 330 | v[15] = blake2b_IV[7] ^ S->f[1]; 331 | 332 | ROUND(0); 333 | ROUND(1); 334 | ROUND(2); 335 | ROUND(3); 336 | ROUND(4); 337 | ROUND(5); 338 | ROUND(6); 339 | ROUND(7); 340 | ROUND(8); 341 | ROUND(9); 342 | ROUND(10); 343 | ROUND(11); 344 | 345 | for (i = 0; i < 8; ++i) { 346 | S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; 347 | } 348 | } 349 | 350 | #undef G 351 | #undef ROUND 352 | 353 | int blake2b_update(blake2b_state *S, const void *pin, size_t inlen) { 354 | const unsigned char *in = (const unsigned char *)pin; 355 | if (inlen > 0) { 356 | size_t left = S->buflen; 357 | size_t fill = BLAKE2B_BLOCKBYTES - left; 358 | if (inlen > fill) { 359 | S->buflen = 0; 360 | memcpy(S->buf + left, in, fill); /* Fill buffer */ 361 | blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); 362 | blake2b_compress(S, S->buf); /* Compress */ 363 | in += fill; 364 | inlen -= fill; 365 | while (inlen > BLAKE2B_BLOCKBYTES) { 366 | blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); 367 | blake2b_compress(S, in); 368 | in += BLAKE2B_BLOCKBYTES; 369 | inlen -= BLAKE2B_BLOCKBYTES; 370 | } 371 | } 372 | memcpy(S->buf + S->buflen, in, inlen); 373 | S->buflen += inlen; 374 | } 375 | return 0; 376 | } 377 | 378 | int blake2b_final(blake2b_state *S, void *out, size_t outlen) { 379 | uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; 380 | size_t i; 381 | 382 | if (out == NULL || outlen < S->outlen) return -1; 383 | 384 | if (blake2b_is_lastblock(S)) return -1; 385 | 386 | blake2b_increment_counter(S, S->buflen); 387 | blake2b_set_lastblock(S); 388 | memset(S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */ 389 | blake2b_compress(S, S->buf); 390 | 391 | for (i = 0; i < 8; ++i) /* Output full hash to temp buffer */ 392 | store64(buffer + sizeof(S->h[i]) * i, S->h[i]); 393 | 394 | memcpy(out, buffer, S->outlen); 395 | secure_zero_memory(buffer, sizeof(buffer)); 396 | return 0; 397 | } 398 | 399 | /* inlen, at least, should be uint64_t. Others can be size_t. */ 400 | int blake2b(void *out, size_t outlen, const void *in, size_t inlen, 401 | const void *key, size_t keylen) { 402 | blake2b_state S[1]; 403 | 404 | /* Verify parameters */ 405 | if (NULL == in && inlen > 0) return -1; 406 | 407 | if (NULL == out) return -1; 408 | 409 | if (NULL == key && keylen > 0) return -1; 410 | 411 | if (!outlen || outlen > BLAKE2B_OUTBYTES) return -1; 412 | 413 | if (keylen > BLAKE2B_KEYBYTES) return -1; 414 | 415 | if (keylen > 0) { 416 | if (blake2b_init_key(S, outlen, key, keylen) < 0) return -1; 417 | } else { 418 | if (blake2b_init(S, outlen) < 0) return -1; 419 | } 420 | 421 | blake2b_update(S, (const uint8_t *)in, inlen); 422 | blake2b_final(S, out, outlen); 423 | return 0; 424 | } 425 | 426 | int blake2(void *out, size_t outlen, const void *in, size_t inlen, 427 | const void *key, size_t keylen) { 428 | return blake2b(out, outlen, in, inlen, key, keylen); 429 | } 430 | 431 | #if defined(SUPERCOP) 432 | int crypto_hash(unsigned char *out, unsigned char *in, 433 | unsigned long long inlen) { 434 | return blake2b(out, BLAKE2B_OUTBYTES, in, inlen, NULL, 0); 435 | } 436 | #endif 437 | 438 | #if defined(BLAKE2B_SELFTEST) 439 | #include 440 | #include "blake2-kat.h" 441 | int main(void) { 442 | uint8_t key[BLAKE2B_KEYBYTES]; 443 | uint8_t buf[BLAKE2_KAT_LENGTH]; 444 | size_t i, step; 445 | 446 | for (i = 0; i < BLAKE2B_KEYBYTES; ++i) key[i] = (uint8_t)i; 447 | 448 | for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) buf[i] = (uint8_t)i; 449 | 450 | /* Test simple API */ 451 | for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { 452 | uint8_t hash[BLAKE2B_OUTBYTES]; 453 | blake2b(hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES); 454 | 455 | if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) { 456 | goto fail; 457 | } 458 | } 459 | 460 | /* Test streaming API */ 461 | for (step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { 462 | for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { 463 | uint8_t hash[BLAKE2B_OUTBYTES]; 464 | blake2b_state S; 465 | uint8_t *p = buf; 466 | size_t mlen = i; 467 | int err = 0; 468 | 469 | if ((err = blake2b_init_key(&S, BLAKE2B_OUTBYTES, key, 470 | BLAKE2B_KEYBYTES)) < 0) { 471 | goto fail; 472 | } 473 | 474 | while (mlen >= step) { 475 | if ((err = blake2b_update(&S, p, step)) < 0) { 476 | goto fail; 477 | } 478 | mlen -= step; 479 | p += step; 480 | } 481 | if ((err = blake2b_update(&S, p, mlen)) < 0) { 482 | goto fail; 483 | } 484 | if ((err = blake2b_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) { 485 | goto fail; 486 | } 487 | 488 | if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) { 489 | goto fail; 490 | } 491 | } 492 | } 493 | 494 | puts("ok"); 495 | return 0; 496 | fail: 497 | puts("error"); 498 | return -1; 499 | } 500 | #endif 501 | -------------------------------------------------------------------------------- /c/ckb_cell_upgrade.c: -------------------------------------------------------------------------------- 1 | #include "ckb_syscalls.h" 2 | #include "protocol.h" 3 | 4 | #define INPUT_SIZE 128 5 | #define SCRIPT_SIZE 32768 6 | 7 | int main() { 8 | uint64_t len = 0; 9 | int ret = ckb_load_cell(NULL, &len, 0, 1, CKB_SOURCE_GROUP_OUTPUT); 10 | if (ret != CKB_INDEX_OUT_OF_BOUND) { 11 | return -1; /* 1 */ 12 | } 13 | 14 | len = 0; 15 | ret = ckb_load_cell(NULL, &len, 0, 0, CKB_SOURCE_GROUP_INPUT); 16 | if (ret != CKB_INDEX_OUT_OF_BOUND) { 17 | return 0; /* 2 */ 18 | } 19 | 20 | /* 3 */ 21 | unsigned char input[INPUT_SIZE]; 22 | uint64_t input_len = INPUT_SIZE; 23 | ret = ckb_load_input(input, &input_len, 0, 0, CKB_SOURCE_INPUT); 24 | if (ret != CKB_SUCCESS) { 25 | return ret; 26 | } 27 | if (input_len > INPUT_SIZE) { 28 | return -100; 29 | } 30 | 31 | unsigned char script[SCRIPT_SIZE]; 32 | len = SCRIPT_SIZE; 33 | ret = ckb_load_script(script, &len, 0); 34 | if (ret != CKB_SUCCESS) { 35 | return ret; 36 | } 37 | if (len > SCRIPT_SIZE) { 38 | return -101; 39 | } 40 | mol_seg_t script_seg; 41 | script_seg.ptr = (uint8_t *)script; 42 | script_seg.size = len; 43 | 44 | if (MolReader_Script_verify(&script_seg, false) != MOL_OK) { 45 | return -102; 46 | } 47 | 48 | mol_seg_t args_seg = MolReader_Script_get_args(&script_seg); 49 | mol_seg_t args_bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); 50 | 51 | if ((input_len == args_bytes_seg.size) && 52 | (memcmp(args_bytes_seg.ptr, input, input_len) == 0)) { 53 | /* 4 */ 54 | return 0; 55 | } 56 | return -103; 57 | } -------------------------------------------------------------------------------- /c/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | common.h 3 | 4 | Defines commonly used high level functions and constants. 5 | */ 6 | 7 | #include "ckb_syscalls.h" 8 | #include "protocol.h" 9 | #include "utils.h" 10 | 11 | /* Common errors */ 12 | #define ERROR_ARGUMENTS_LEN -1 13 | #define ERROR_ENCODING -2 14 | #define ERROR_SYSCALL -3 15 | #define ERROR_SECP_RECOVER_PUBKEY -11 16 | #define ERROR_SECP_VERIFICATION -12 17 | #define ERROR_SECP_PARSE_PUBKEY -13 18 | #define ERROR_SECP_PARSE_SIGNATURE -14 19 | #define ERROR_SECP_SERIALIZE_PUBKEY -15 20 | #define ERROR_SCRIPT_TOO_LONG -21 21 | #define ERROR_WITNESS_SIZE -22 22 | #define ERROR_INCORRECT_SINCE_FLAGS -23 23 | #define ERROR_INCORRECT_SINCE_VALUE -24 24 | #define ERROR_PUBKEY_BLAKE160_HASH -31 25 | 26 | /* anyone can pay errors */ 27 | #define ERROR_OVERFLOW -41 28 | #define ERROR_OUTPUT_AMOUNT_NOT_ENOUGH -42 29 | #define ERROR_TOO_MUCH_TYPE_HASH_INPUTS -43 30 | #define ERROR_NO_PAIR -44 31 | #define ERROR_DUPLICATED_INPUTS -45 32 | #define ERROR_DUPLICATED_OUTPUTS -46 33 | 34 | /* since */ 35 | #define SINCE_VALUE_BITS 56 36 | #define SINCE_VALUE_MASK 0x00ffffffffffffff 37 | #define SINCE_EPOCH_FRACTION_FLAG 0b00100000 38 | 39 | /* calculate inputs length */ 40 | int calculate_inputs_len() { 41 | uint64_t len = 0; 42 | /* lower bound, at least tx has one input */ 43 | int lo = 0; 44 | /* higher bound */ 45 | int hi = 4; 46 | int ret; 47 | /* try to load input until failing to increase lo and hi */ 48 | while (1) { 49 | ret = ckb_load_input_by_field(NULL, &len, 0, hi, CKB_SOURCE_INPUT, 50 | CKB_INPUT_FIELD_SINCE); 51 | if (ret == CKB_SUCCESS) { 52 | lo = hi; 53 | hi *= 2; 54 | } else { 55 | break; 56 | } 57 | } 58 | 59 | /* now we get our lower bound and higher bound, 60 | count number of inputs by binary search */ 61 | int i; 62 | while (lo + 1 != hi) { 63 | i = (lo + hi) / 2; 64 | ret = ckb_load_input_by_field(NULL, &len, 0, i, CKB_SOURCE_INPUT, 65 | CKB_INPUT_FIELD_SINCE); 66 | if (ret == CKB_SUCCESS) { 67 | lo = i; 68 | } else { 69 | hi = i; 70 | } 71 | } 72 | /* now lo is last input index and hi is length of inputs */ 73 | return hi; 74 | } 75 | 76 | /* Extract lock from WitnessArgs */ 77 | int extract_witness_lock(uint8_t *witness, uint64_t len, 78 | mol_seg_t *lock_bytes_seg) { 79 | mol_seg_t witness_seg; 80 | witness_seg.ptr = witness; 81 | witness_seg.size = len; 82 | 83 | if (MolReader_WitnessArgs_verify(&witness_seg, false) != MOL_OK) { 84 | return ERROR_ENCODING; 85 | } 86 | mol_seg_t lock_seg = MolReader_WitnessArgs_get_lock(&witness_seg); 87 | 88 | if (MolReader_BytesOpt_is_none(&lock_seg)) { 89 | return ERROR_ENCODING; 90 | } 91 | *lock_bytes_seg = MolReader_Bytes_raw_bytes(&lock_seg); 92 | return CKB_SUCCESS; 93 | } 94 | 95 | /* check since, 96 | for all inputs the since field must have the exactly same flags with the since 97 | constraint, and the value of since must greater or equals than the since 98 | contstaint */ 99 | int check_since(uint64_t since) { 100 | size_t i = 0, len; 101 | uint64_t input_since; 102 | /* the 8 msb is flag */ 103 | uint8_t since_flags = since >> SINCE_VALUE_BITS; 104 | uint64_t since_value = since & SINCE_VALUE_MASK; 105 | int ret; 106 | while (1) { 107 | len = sizeof(uint64_t); 108 | ret = 109 | ckb_load_input_by_field(&input_since, &len, 0, i, 110 | CKB_SOURCE_GROUP_INPUT, CKB_INPUT_FIELD_SINCE); 111 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 112 | break; 113 | } 114 | if (ret != CKB_SUCCESS || len != sizeof(uint64_t)) { 115 | return ERROR_SYSCALL; 116 | } 117 | uint8_t input_since_flags = input_since >> SINCE_VALUE_BITS; 118 | uint64_t input_since_value = input_since & SINCE_VALUE_MASK; 119 | if (since_flags != input_since_flags) { 120 | return ERROR_INCORRECT_SINCE_FLAGS; 121 | } 122 | if (input_since_flags == SINCE_EPOCH_FRACTION_FLAG) { 123 | ret = epoch_number_with_fraction_cmp(input_since_value, since_value); 124 | if (ret < 0) { 125 | return ERROR_INCORRECT_SINCE_VALUE; 126 | } 127 | } else if (input_since_value < since_value) { 128 | return ERROR_INCORRECT_SINCE_VALUE; 129 | } 130 | i += 1; 131 | } 132 | return CKB_SUCCESS; 133 | } 134 | -------------------------------------------------------------------------------- /c/defs.h: -------------------------------------------------------------------------------- 1 | #define MAX_UINT64 0xffffffffffffffffUL 2 | #define MAX_UINT128 (((uint128_t)MAX_UINT64 << 64) + MAX_UINT64) 3 | 4 | typedef unsigned __int128 uint128_t; 5 | -------------------------------------------------------------------------------- /c/dump_secp256k1_data.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "blake2b.h" 3 | 4 | /* 5 | * We are including secp256k1 implementation directly so gcc can strip 6 | * unused functions. For some unknown reasons, if we link in libsecp256k1.a 7 | * directly, the final binary will include all functions rather than those used. 8 | */ 9 | #define HAVE_CONFIG_H 1 10 | #include 11 | 12 | #define ERROR_IO -1 13 | 14 | int main(int argc, char* argv[]) { 15 | size_t pre_size = sizeof(secp256k1_ecmult_static_pre_context); 16 | size_t pre128_size = sizeof(secp256k1_ecmult_static_pre128_context); 17 | 18 | FILE* fp_data = fopen("specs/cells/secp256k1_data", "wb"); 19 | if (!fp_data) { 20 | return ERROR_IO; 21 | } 22 | fwrite(secp256k1_ecmult_static_pre_context, pre_size, 1, fp_data); 23 | fwrite(secp256k1_ecmult_static_pre128_context, pre128_size, 1, fp_data); 24 | fclose(fp_data); 25 | 26 | FILE* fp = fopen("build/secp256k1_data_info.h", "w"); 27 | if (!fp) { 28 | return ERROR_IO; 29 | } 30 | 31 | fprintf(fp, "#ifndef CKB_SECP256K1_DATA_INFO_H_\n"); 32 | fprintf(fp, "#define CKB_SECP256K1_DATA_INFO_H_\n"); 33 | fprintf(fp, "#define CKB_SECP256K1_DATA_SIZE %ld\n", pre_size + pre128_size); 34 | fprintf(fp, "#define CKB_SECP256K1_DATA_PRE_SIZE %ld\n", pre_size); 35 | fprintf(fp, "#define CKB_SECP256K1_DATA_PRE128_SIZE %ld\n", pre128_size); 36 | 37 | blake2b_state blake2b_ctx; 38 | uint8_t hash[32]; 39 | blake2b_init(&blake2b_ctx, 32); 40 | blake2b_update(&blake2b_ctx, secp256k1_ecmult_static_pre_context, pre_size); 41 | blake2b_update(&blake2b_ctx, secp256k1_ecmult_static_pre128_context, 42 | pre128_size); 43 | blake2b_final(&blake2b_ctx, hash, 32); 44 | 45 | fprintf(fp, "static uint8_t ckb_secp256k1_data_hash[32] = {\n "); 46 | for (int i = 0; i < 32; i++) { 47 | fprintf(fp, "%u", hash[i]); 48 | if (i != 31) { 49 | fprintf(fp, ", "); 50 | } 51 | } 52 | fprintf(fp, "\n};\n"); 53 | fprintf(fp, "#endif\n"); 54 | fclose(fp); 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /c/keccak256.h: -------------------------------------------------------------------------------- 1 | /* sha3 - an implementation of Secure Hash Algorithm 3 (Keccak). 2 | * based on the 3 | * The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011 4 | * by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche 5 | * 6 | * Copyright: 2013 Aleksey Kravchenko 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a 9 | * copy of this software and associated documentation files (the "Software"), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | * and/or sell copies of the Software, and to permit persons to whom the 13 | * Software is furnished to do so. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 17 | * or FITNESS FOR A PARTICULAR PURPOSE. Use this program at your own risk! 18 | */ 19 | 20 | #ifndef __KECCAK256_H_ 21 | #define __KECCAK256_H_ 22 | 23 | #include 24 | #include 25 | 26 | #define sha3_max_permutation_size 25 27 | #define sha3_max_rate_in_qwords 24 28 | 29 | typedef struct SHA3_CTX { 30 | /* 1600 bits algorithm hashing state */ 31 | uint64_t hash[sha3_max_permutation_size]; 32 | /* 1536-bit buffer for leftovers */ 33 | uint64_t message[sha3_max_rate_in_qwords]; 34 | /* count of bytes in the message[] buffer */ 35 | uint16_t rest; 36 | /* size of a message block processed at once */ 37 | // unsigned block_size; 38 | } SHA3_CTX; 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif /* __cplusplus */ 43 | 44 | void keccak_init(SHA3_CTX *ctx); 45 | void keccak_update(SHA3_CTX *ctx, unsigned char *msg, uint16_t size); 46 | void keccak_final(SHA3_CTX *ctx, unsigned char *result); 47 | 48 | #ifdef __cplusplus 49 | } 50 | #endif /* __cplusplus */ 51 | 52 | #endif /* __KECCAK256_H_ */ 53 | 54 | // keccak256 implementation 55 | 56 | #define BLOCK_SIZE ((1600 - 256 * 2) / 8) 57 | 58 | #define I64(x) x##LL 59 | #define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n)))) 60 | #define le2me_64(x) (x) 61 | #define IS_ALIGNED_64(p) (0 == (7 & ((const char *)(p) - (const char *)0))) 62 | #define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) 63 | 64 | /* constants */ 65 | 66 | // const uint8_t round_constant_info[] PROGMEM = { 67 | // const uint8_t constants[] PROGMEM = { 68 | const uint8_t constants[] = { 69 | 70 | 1, 71 | 26, 72 | 94, 73 | 112, 74 | 31, 75 | 33, 76 | 121, 77 | 85, 78 | 14, 79 | 12, 80 | 53, 81 | 38, 82 | 63, 83 | 79, 84 | 93, 85 | 83, 86 | 82, 87 | 72, 88 | 22, 89 | 102, 90 | 121, 91 | 88, 92 | 33, 93 | 116, 94 | //}; 95 | 96 | // const uint8_t pi_transform[] PROGMEM = { 97 | 1, 98 | 6, 99 | 9, 100 | 22, 101 | 14, 102 | 20, 103 | 2, 104 | 12, 105 | 13, 106 | 19, 107 | 23, 108 | 15, 109 | 4, 110 | 24, 111 | 21, 112 | 8, 113 | 16, 114 | 5, 115 | 3, 116 | 18, 117 | 17, 118 | 11, 119 | 7, 120 | 10, 121 | //}; 122 | 123 | // const uint8_t rhoTransforms[] PROGMEM = { 124 | 1, 125 | 62, 126 | 28, 127 | 27, 128 | 36, 129 | 44, 130 | 6, 131 | 55, 132 | 20, 133 | 3, 134 | 10, 135 | 43, 136 | 25, 137 | 39, 138 | 41, 139 | 45, 140 | 15, 141 | 21, 142 | 8, 143 | 18, 144 | 2, 145 | 61, 146 | 56, 147 | 14, 148 | }; 149 | 150 | #define TYPE_ROUND_INFO 0 151 | #define TYPE_PI_TRANSFORM 24 152 | #define TYPE_RHO_TRANSFORM 48 153 | 154 | uint8_t getConstant(uint8_t type, uint8_t index) { 155 | return constants[type + index]; 156 | // return pgm_read_byte(&constants[type + index]); 157 | } 158 | 159 | static uint64_t get_round_constant(uint8_t round) { 160 | uint64_t result = 0; 161 | 162 | // uint8_t roundInfo = pgm_read_byte(&round_constant_info[round]); 163 | uint8_t roundInfo = getConstant(TYPE_ROUND_INFO, round); 164 | if (roundInfo & (1 << 6)) { 165 | result |= ((uint64_t)1 << 63); 166 | } 167 | if (roundInfo & (1 << 5)) { 168 | result |= ((uint64_t)1 << 31); 169 | } 170 | if (roundInfo & (1 << 4)) { 171 | result |= ((uint64_t)1 << 15); 172 | } 173 | if (roundInfo & (1 << 3)) { 174 | result |= ((uint64_t)1 << 7); 175 | } 176 | if (roundInfo & (1 << 2)) { 177 | result |= ((uint64_t)1 << 3); 178 | } 179 | if (roundInfo & (1 << 1)) { 180 | result |= ((uint64_t)1 << 1); 181 | } 182 | if (roundInfo & (1 << 0)) { 183 | result |= ((uint64_t)1 << 0); 184 | } 185 | 186 | return result; 187 | } 188 | 189 | /* Initializing a sha3 context for given number of output bits */ 190 | void keccak_init(SHA3_CTX *ctx) { 191 | /* NB: The Keccak capacity parameter = bits * 2 */ 192 | 193 | memset(ctx, 0, sizeof(SHA3_CTX)); 194 | } 195 | 196 | /* Keccak theta() transformation */ 197 | static void keccak_theta(uint64_t *A) { 198 | uint64_t C[5], D[5]; 199 | 200 | for (uint8_t i = 0; i < 5; i++) { 201 | C[i] = A[i]; 202 | for (uint8_t j = 5; j < 25; j += 5) { 203 | C[i] ^= A[i + j]; 204 | } 205 | } 206 | 207 | for (uint8_t i = 0; i < 5; i++) { 208 | D[i] = ROTL64(C[(i + 1) % 5], 1) ^ C[(i + 4) % 5]; 209 | } 210 | 211 | for (uint8_t i = 0; i < 5; i++) { 212 | // for (uint8_t j = 0; j < 25; j += 5) { 213 | for (uint8_t j = 0; j < 25; j += 5) { 214 | A[i + j] ^= D[i]; 215 | } 216 | } 217 | } 218 | 219 | /* Keccak pi() transformation */ 220 | static void keccak_pi(uint64_t *A) { 221 | uint64_t A1 = A[1]; 222 | // for (uint8_t i = 1; i < sizeof(pi_transform); i++) { 223 | for (uint8_t i = 1; i < 24; i++) { 224 | // A[pgm_read_byte(&pi_transform[i - 1])] = 225 | // A[pgm_read_byte(&pi_transform[i])]; 226 | A[getConstant(TYPE_PI_TRANSFORM, i - 1)] = 227 | A[getConstant(TYPE_PI_TRANSFORM, i)]; 228 | } 229 | A[10] = A1; 230 | /* note: A[ 0] is left as is */ 231 | } 232 | 233 | /* 234 | ketch uses 30084 bytes (93%) of program storage space. Maximum is 32256 bytes. 235 | Global variables use 743 bytes (36%) of dynamic memory, leaving 1305 bytes for 236 | local variables. Maximum is 2048 bytes. 237 | */ 238 | /* Keccak chi() transformation */ 239 | static void keccak_chi(uint64_t *A) { 240 | for (uint8_t i = 0; i < 25; i += 5) { 241 | uint64_t A0 = A[0 + i], A1 = A[1 + i]; 242 | A[0 + i] ^= ~A1 & A[2 + i]; 243 | A[1 + i] ^= ~A[2 + i] & A[3 + i]; 244 | A[2 + i] ^= ~A[3 + i] & A[4 + i]; 245 | A[3 + i] ^= ~A[4 + i] & A0; 246 | A[4 + i] ^= ~A0 & A1; 247 | } 248 | } 249 | 250 | static void sha3_permutation(uint64_t *state) { 251 | // for (uint8_t round = 0; round < sizeof(round_constant_info); round++) { 252 | for (uint8_t round = 0; round < 24; round++) { 253 | keccak_theta(state); 254 | 255 | /* apply Keccak rho() transformation */ 256 | for (uint8_t i = 1; i < 25; i++) { 257 | // state[i] = ROTL64(state[i], pgm_read_byte(&rhoTransforms[i - 1])); 258 | state[i] = ROTL64(state[i], getConstant(TYPE_RHO_TRANSFORM, i - 1)); 259 | } 260 | 261 | keccak_pi(state); 262 | keccak_chi(state); 263 | 264 | /* apply iota(state, round) */ 265 | *state ^= get_round_constant(round); 266 | } 267 | } 268 | 269 | /** 270 | * The core transformation. Process the specified block of data. 271 | * 272 | * @param hash the algorithm state 273 | * @param block the message block to process 274 | * @param block_size the size of the processed block in bytes 275 | */ 276 | static void sha3_process_block(uint64_t hash[25], const uint64_t *block) { 277 | for (uint8_t i = 0; i < 17; i++) { 278 | hash[i] ^= le2me_64(block[i]); 279 | } 280 | 281 | /* make a permutation of the hash */ 282 | sha3_permutation(hash); 283 | } 284 | 285 | //#define SHA3_FINALIZED 0x80000000 286 | //#define SHA3_FINALIZED 0x8000 287 | 288 | /** 289 | * Calculate message hash. 290 | * Can be called repeatedly with chunks of the message to be hashed. 291 | * 292 | * @param ctx the algorithm context containing current hashing state 293 | * @param msg message chunk 294 | * @param size length of the message chunk 295 | */ 296 | void keccak_update(SHA3_CTX *ctx, unsigned char *msg, uint16_t size) { 297 | uint16_t idx = (uint16_t)ctx->rest; 298 | 299 | // if (ctx->rest & SHA3_FINALIZED) return; /* too late for additional input */ 300 | ctx->rest = (unsigned)((ctx->rest + size) % BLOCK_SIZE); 301 | 302 | /* fill partial block */ 303 | if (idx) { 304 | uint16_t left = BLOCK_SIZE - idx; 305 | memcpy((char *)ctx->message + idx, msg, (size < left ? size : left)); 306 | if (size < left) return; 307 | 308 | /* process partial block */ 309 | sha3_process_block(ctx->hash, ctx->message); 310 | msg += left; 311 | size -= left; 312 | } 313 | 314 | while (size >= BLOCK_SIZE) { 315 | uint64_t *aligned_message_block; 316 | if (IS_ALIGNED_64(msg)) { 317 | // the most common case is processing of an already aligned message 318 | // without copying it 319 | aligned_message_block = (uint64_t *)(void *)msg; 320 | } else { 321 | memcpy(ctx->message, msg, BLOCK_SIZE); 322 | aligned_message_block = ctx->message; 323 | } 324 | 325 | sha3_process_block(ctx->hash, aligned_message_block); 326 | msg += BLOCK_SIZE; 327 | size -= BLOCK_SIZE; 328 | } 329 | 330 | if (size) { 331 | memcpy(ctx->message, msg, size); /* save leftovers */ 332 | } 333 | } 334 | 335 | /** 336 | * Store calculated hash into the given array. 337 | * 338 | * @param ctx the algorithm context containing current hashing state 339 | * @param result calculated hash in binary form 340 | */ 341 | void keccak_final(SHA3_CTX *ctx, unsigned char *result) { 342 | uint16_t digest_length = 100 - BLOCK_SIZE / 2; 343 | 344 | // if (!(ctx->rest & SHA3_FINALIZED)) { 345 | /* clear the rest of the data queue */ 346 | memset((char *)ctx->message + ctx->rest, 0, BLOCK_SIZE - ctx->rest); 347 | ((char *)ctx->message)[ctx->rest] |= 0x01; 348 | ((char *)ctx->message)[BLOCK_SIZE - 1] |= 0x80; 349 | 350 | /* process final block */ 351 | sha3_process_block(ctx->hash, ctx->message); 352 | // ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ 353 | // } 354 | 355 | if (result) { 356 | me64_to_le_str(result, ctx->hash, digest_length); 357 | } 358 | } -------------------------------------------------------------------------------- /c/overflow_add.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | 3 | int uint64_overflow_add(uint64_t *result, uint64_t a, uint64_t b) { 4 | if (MAX_UINT64 - a < b) { 5 | /* overflow */ 6 | return 1; 7 | } 8 | *result = a + b; 9 | return 0; 10 | } 11 | 12 | int uint128_overflow_add(uint128_t *result, uint128_t a, uint128_t b) { 13 | if (MAX_UINT128 - a < b) { 14 | /* overflow */ 15 | return 1; 16 | } 17 | *result = a + b; 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /c/quick_pow10.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | 3 | #define MAX_POW_N 19 4 | #define UINT128_MAX_POW_N 38 5 | 6 | int quick_pow10(int n, uint64_t *result) { 7 | static uint64_t pow10[MAX_POW_N + 1] = { 8 | 1, 9 | 10, 10 | 100, 11 | 1000, 12 | 10000, 13 | 100000, 14 | 1000000, 15 | 10000000, 16 | 100000000, 17 | 1000000000, 18 | 10000000000, 19 | 100000000000, 20 | 1000000000000, 21 | 10000000000000, 22 | 100000000000000, 23 | 1000000000000000, 24 | 10000000000000000, 25 | 100000000000000000, 26 | 1000000000000000000, 27 | 10000000000000000000U, 28 | }; 29 | 30 | if (n > MAX_POW_N) { 31 | return 1; 32 | } 33 | 34 | *result = pow10[n]; 35 | return 0; 36 | } 37 | 38 | int uint128_quick_pow10(int n, uint128_t *result) { 39 | if (n > UINT128_MAX_POW_N) { 40 | return 1; 41 | } 42 | 43 | int ret; 44 | uint64_t mid_result; 45 | if (n <= MAX_POW_N) { 46 | ret = quick_pow10(n, &mid_result); 47 | if (ret) { 48 | return ret; 49 | } 50 | *result = mid_result; 51 | } else { 52 | /* a^(x+y) -> a^x * a^y */ 53 | ret = quick_pow10(n - MAX_POW_N, &mid_result); 54 | if (ret) { 55 | return ret; 56 | } 57 | *result = mid_result; 58 | ret = quick_pow10(MAX_POW_N, &mid_result); 59 | if (ret) { 60 | return ret; 61 | } 62 | *result *= mid_result; 63 | } 64 | 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /c/secp256k1_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef CKB_SECP256K1_HELPER_H_ 2 | #define CKB_SECP256K1_HELPER_H_ 3 | 4 | #include "ckb_syscalls.h" 5 | #include "secp256k1_data_info.h" 6 | 7 | #define CKB_SECP256K1_HELPER_ERROR_LOADING_DATA -101 8 | #define CKB_SECP256K1_HELPER_ERROR_ILLEGAL_CALLBACK -102 9 | #define CKB_SECP256K1_HELPER_ERROR_ERROR_CALLBACK -103 10 | 11 | /* 12 | * We are including secp256k1 implementation directly so gcc can strip 13 | * unused functions. For some unknown reasons, if we link in libsecp256k1.a 14 | * directly, the final binary will include all functions rather than those used. 15 | */ 16 | #define HAVE_CONFIG_H 1 17 | #define USE_EXTERNAL_DEFAULT_CALLBACKS 18 | #include 19 | 20 | void secp256k1_default_illegal_callback_fn(const char* str, void* data) { 21 | (void)str; 22 | (void)data; 23 | ckb_exit(CKB_SECP256K1_HELPER_ERROR_ILLEGAL_CALLBACK); 24 | } 25 | 26 | void secp256k1_default_error_callback_fn(const char* str, void* data) { 27 | (void)str; 28 | (void)data; 29 | ckb_exit(CKB_SECP256K1_HELPER_ERROR_ERROR_CALLBACK); 30 | } 31 | 32 | /* 33 | * data should at least be CKB_SECP256K1_DATA_SIZE big 34 | * so as to hold all loaded data. 35 | */ 36 | int ckb_secp256k1_custom_verify_only_initialize(secp256k1_context* context, 37 | void* data) { 38 | size_t index = 0; 39 | int running = 1; 40 | while (running && index < SIZE_MAX) { 41 | uint64_t len = 32; 42 | uint8_t hash[32]; 43 | 44 | int ret = ckb_load_cell_by_field(hash, &len, 0, index, CKB_SOURCE_CELL_DEP, 45 | CKB_CELL_FIELD_DATA_HASH); 46 | switch (ret) { 47 | case CKB_ITEM_MISSING: 48 | break; 49 | case CKB_SUCCESS: 50 | if (memcmp(ckb_secp256k1_data_hash, hash, 32) == 0) { 51 | /* Found a match, load data here */ 52 | len = CKB_SECP256K1_DATA_SIZE; 53 | ret = ckb_load_cell_data(data, &len, 0, index, CKB_SOURCE_CELL_DEP); 54 | if (ret != CKB_SUCCESS || len != CKB_SECP256K1_DATA_SIZE) { 55 | return CKB_SECP256K1_HELPER_ERROR_LOADING_DATA; 56 | } 57 | running = 0; 58 | } 59 | break; 60 | default: 61 | return CKB_SECP256K1_HELPER_ERROR_LOADING_DATA; 62 | } 63 | if (running) { 64 | index++; 65 | } 66 | } 67 | if (index == SIZE_MAX) { 68 | return CKB_SECP256K1_HELPER_ERROR_LOADING_DATA; 69 | } 70 | 71 | context->illegal_callback = default_illegal_callback; 72 | context->error_callback = default_error_callback; 73 | 74 | secp256k1_ecmult_context_init(&context->ecmult_ctx); 75 | secp256k1_ecmult_gen_context_init(&context->ecmult_gen_ctx); 76 | 77 | /* Recasting data to (uint8_t*) for pointer math */ 78 | uint8_t* p = data; 79 | secp256k1_ge_storage(*pre_g)[] = (secp256k1_ge_storage(*)[])p; 80 | secp256k1_ge_storage(*pre_g_128)[] = 81 | (secp256k1_ge_storage(*)[])(&p[CKB_SECP256K1_DATA_PRE_SIZE]); 82 | context->ecmult_ctx.pre_g = pre_g; 83 | context->ecmult_ctx.pre_g_128 = pre_g_128; 84 | 85 | return 0; 86 | } 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /c/secp256k1_keccak256_lock.h: -------------------------------------------------------------------------------- 1 | #include "bech32.h" 2 | #include "ckb_syscalls.h" 3 | #include "common.h" 4 | #include "keccak256.h" 5 | #include "protocol.h" 6 | #include "secp256k1_helper.h" 7 | 8 | #define BLAKE2B_BLOCK_SIZE 32 9 | #define BLAKE160_SIZE 20 10 | #define PUBKEY_SIZE 65 // ETH address uncompress pub key 11 | #define TEMP_SIZE 32768 12 | #define RECID_INDEX 64 13 | /* 32 KB */ 14 | #define MAX_WITNESS_SIZE 32768 15 | #define SCRIPT_SIZE 32768 16 | #define SIGNATURE_SIZE 65 17 | 18 | #define CKB_ADDRESS_PREFIX "ckt" 19 | 20 | #define MAX_OUTPUT_LENGTH 64 21 | 22 | #define ERROR_TOO_MANY_OUTPUT_CELLS -18 23 | 24 | #if (MAX_WITNESS_SIZE > TEMP_SIZE) || (SCRIPT_SIZE > TEMP_SIZE) 25 | #error "Temp buffer is not big enough!" 26 | #endif 27 | 28 | /* 29 | * Format CKB address by lock script, and get the keccak256 hash of CKB address. 30 | * 31 | * Arguments: 32 | * script_seg, lock script segment 33 | * hash, the returned hash of ckb address 34 | * 35 | */ 36 | int hash_address(mol_seg_t *script_seg, unsigned char *hash) { 37 | mol_seg_t args_seg = MolReader_Script_get_args(script_seg); 38 | mol_seg_t args_bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); 39 | 40 | mol_seg_t code_hash_seg = MolReader_Script_get_code_hash(script_seg); 41 | mol_seg_t hash_type_seg = MolReader_Script_get_hash_type(script_seg); 42 | 43 | /* the type id of secp256k1_blake160_signhash_all: 44 | * 0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8 */ 45 | unsigned char SECP256K1_BLAKE160_SIGHASH_ALL_TYPE_HASH[32] = { 46 | 0x9b, 0xd7, 0xe0, 0x6f, 0x3e, 0xcf, 0x4b, 0xe0, 0xf2, 0xfc, 0xd2, 47 | 0x18, 0x8b, 0x23, 0xf1, 0xb9, 0xfc, 0xc8, 0x8e, 0x5d, 0x4b, 0x65, 48 | 0xa8, 0x63, 0x7b, 0x17, 0x72, 0x3b, 0xbd, 0xa3, 0xcc, 0xe8}; 49 | /* the type id of secp256k1_blake160_multisig_all: 50 | * 0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8 */ 51 | unsigned char SECP256K1_BLAKE160_MULTISIG_ALL_TYPE_HASH[32] = { 52 | 0x5c, 0x50, 0x69, 0xeb, 0x08, 0x57, 0xef, 0xc6, 0x5e, 0x1b, 0xca, 53 | 0x0c, 0x07, 0xdf, 0x34, 0xc3, 0x16, 0x63, 0xb3, 0x62, 0x2f, 0xd3, 54 | 0x87, 0x6c, 0x87, 0x63, 0x20, 0xfc, 0x96, 0x34, 0xe2, 0xa8}; 55 | 56 | size_t payload_len = 0; 57 | size_t data_len = 0; 58 | unsigned char payload[1024]; 59 | unsigned char ckb_address[1024]; 60 | unsigned char data[1024]; 61 | 62 | unsigned char formated_ckb_address[17]; 63 | int ret = 0; 64 | 65 | if (code_hash_seg.size == 0) { 66 | /* empty lock script, consider address is unknow */ 67 | data_len = 7; 68 | memcpy(ckb_address, "unknown", 7); 69 | } else { 70 | if (memcmp(code_hash_seg.ptr, SECP256K1_BLAKE160_SIGHASH_ALL_TYPE_HASH, 71 | code_hash_seg.size) == 0) { 72 | /* generate short ckb address */ 73 | payload[payload_len++] = 0x01; 74 | payload[payload_len++] = 0x00; 75 | } else if (memcmp(code_hash_seg.ptr, 76 | SECP256K1_BLAKE160_MULTISIG_ALL_TYPE_HASH, 77 | code_hash_seg.size) == 0) { 78 | /* generate short ckb address */ 79 | payload[payload_len++] = 0x01; 80 | payload[payload_len++] = 0x01; 81 | } else { 82 | if (*hash_type_seg.ptr == 0x01) { 83 | payload[payload_len++] = 0x04; 84 | } else { 85 | payload[payload_len++] = 0x02; 86 | } 87 | memcpy((void *)(payload + payload_len), code_hash_seg.ptr, 88 | code_hash_seg.size); 89 | payload_len += code_hash_seg.size; 90 | } 91 | memcpy(payload + payload_len, args_bytes_seg.ptr, args_bytes_seg.size); 92 | payload_len += args_bytes_seg.size; 93 | 94 | /* generate ckb address using bech32 lib */ 95 | ret = convert_bits(data, &data_len, 5, payload, payload_len, 8, 1); 96 | if (ret == 0) return -10; 97 | ret = 98 | bech32_encode((char *)&ckb_address, CKB_ADDRESS_PREFIX, data, data_len); 99 | if (ret == 0) return -11; 100 | data_len += 10; 101 | 102 | /* shorten ckb address to 17 characters, using ... to substitute the middle 103 | * characters */ 104 | if (data_len <= 17) { 105 | memcpy(formated_ckb_address, ckb_address, data_len); 106 | } else { 107 | memcpy(formated_ckb_address, ckb_address, 7); 108 | memcpy(formated_ckb_address + 7, "...", 3); 109 | memcpy(formated_ckb_address + 10, ckb_address + (data_len - 7), 7); 110 | data_len = 17; 111 | } 112 | } 113 | 114 | SHA3_CTX sha3_ctx; 115 | keccak_init(&sha3_ctx); 116 | keccak_update(&sha3_ctx, formated_ckb_address, data_len); 117 | keccak_final(&sha3_ctx, hash); 118 | 119 | return CKB_SUCCESS; 120 | } 121 | 122 | /* 123 | * Format amount to number with 8 decimals and ended with "CKB", get the 124 | * keccak256 hash of the amount string 125 | * 126 | * Arguments: 127 | * capacity, the capacity of the output cell 128 | * hash, the returned keccak256 hash of amount string 129 | */ 130 | int hash_amount(uint64_t capacity, unsigned char *hash) { 131 | unsigned char amount[100]; 132 | 133 | /* format capacity */ 134 | int len = snprintf((char *)&amount, 100, "%.8fCKB", capacity / 100000000.0); 135 | 136 | /* calculate keccak256 hash of amount */ 137 | SHA3_CTX sha3_ctx; 138 | keccak_init(&sha3_ctx); 139 | keccak_update(&sha3_ctx, amount, len); 140 | keccak_final(&sha3_ctx, hash); 141 | 142 | return CKB_SUCCESS; 143 | } 144 | 145 | /* 146 | * Calculate the EIP712 typed data hash for a CKB transcation. 147 | * The format of typed data is as follows: 148 | * { 149 | * domain: { 150 | * chainId: 1, 151 | * name: 'ckb.pw', 152 | * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', 153 | * version: '1', 154 | * }, 155 | * message: { 156 | * hash: 157 | * '0x545529d4464064d8394c557afb06f489e7044a63984c6113385431d93dcffa1b', fee: 158 | * '0.00100000CKB', 'input-sum': '100.00000000CKB', to: [ 159 | * { 160 | * address: 'ckb1qyq...qwstnwm', 161 | * amount: '100.00000000CKB', 162 | * }, 163 | * { 164 | * address: 'ckb1qft...xudxcyg', 165 | * amount: '799.99800000CKB', 166 | * }, 167 | * ], 168 | * }, 169 | * primaryType: 'CKBTransaction', 170 | * types: { 171 | * EIP712Domain: [ 172 | * { name: 'name', type: 'string' }, 173 | * { name: 'version', type: 'string' }, 174 | * { name: 'chainId', type: 'uint256' }, 175 | * { name: 'verifyingContract', type: 'address' }, 176 | * ], 177 | * CKBTransaction: [ 178 | * { name: 'hash', type: 'bytes32' }, 179 | * { name: 'fee', type: 'string' }, 180 | * { name: 'input-sum', type: 'string' }, 181 | * { name: 'to', type: 'Output[]' }, 182 | * ], 183 | * Output: [ 184 | * { name: 'address', type: 'string' }, 185 | * { name: 'amount', type: 'string' }, 186 | * ], 187 | * }, 188 | * } 189 | * 190 | * Arguments: 191 | * tx_message, the ethereum personal signed hash of transaction body. 192 | * type_data_hash, the returned calculated typed data hash 193 | * 194 | */ 195 | int calculate_typed_data(unsigned char *tx_message, 196 | unsigned char *typed_data_hash) { 197 | int ret; 198 | uint64_t len = 0; 199 | size_t index = 0; 200 | uint64_t input_capacities = 0; 201 | uint64_t output_capacities = 0; 202 | uint64_t tx_fee = 0; 203 | 204 | unsigned char script[SCRIPT_SIZE]; 205 | mol_seg_t script_seg; 206 | 207 | /** 208 | * 209 | * hard coded hash 210 | */ 211 | /* typed data prefix */ 212 | unsigned char TYPEDDATA_PREFIX[2] = {0x19, 0x01}; 213 | /* hash for type CKBTransaction, is equal to 214 | * web3utils.sha3('CKBTransaction(bytes32 hash,string fee,string 215 | * input-sum,Output[] to)Output(string address,string amount)') */ 216 | unsigned char CKBTRANSACTION_TYPEHASH[BLAKE2B_BLOCK_SIZE] = { 217 | 0x17, 0xe4, 0x04, 0xd0, 0xcd, 0xcc, 0x43, 0x1e, 0xe6, 0xdf, 0x80, 218 | 0x7a, 0xbc, 0xcc, 0x69, 0x5d, 0x95, 0xd0, 0x38, 0xf5, 0x76, 0x47, 219 | 0xe2, 0xef, 0x92, 0xb9, 0x68, 0x66, 0xca, 0xe5, 0x9d, 0x04}; 220 | /* hash for type Output, is equal to web3utils.sha3('Output(string 221 | * address,string amount)') */ 222 | unsigned char OUTPUT_TYPEHASH[BLAKE2B_BLOCK_SIZE] = { 223 | 0xef, 0xdd, 0x9a, 0xc6, 0xc9, 0x8f, 0xcb, 0xab, 0xc5, 0x2e, 0xf1, 224 | 0xd8, 0xa4, 0xd3, 0xac, 0xcd, 0x43, 0x96, 0x36, 0x2a, 0x21, 0x1c, 225 | 0xbf, 0x7a, 0x3c, 0x20, 0xc2, 0x89, 0x22, 0x08, 0x19, 0x13}; 226 | /* hash for domain separator, is equal to web3utils.sha3("EIP712Domain(string 227 | * name,string version,uint256 chainId,address verifyingContract)") */ 228 | unsigned char DOMAIN_SEPARATOR[BLAKE2B_BLOCK_SIZE] = { 229 | 0xec, 0x9e, 0x64, 0xcb, 0x49, 0x31, 0x37, 0x85, 0x0e, 0x3d, 0x5d, 230 | 0x47, 0x3c, 0xa1, 0x09, 0xea, 0xe1, 0x47, 0xad, 0xb8, 0xa6, 0xbf, 231 | 0x46, 0x0b, 0xf2, 0x06, 0xe9, 0x0f, 0x62, 0x64, 0x2e, 0x3f, 232 | }; 233 | 234 | unsigned char address_hash[BLAKE2B_BLOCK_SIZE]; 235 | unsigned char amount_hash[BLAKE2B_BLOCK_SIZE]; 236 | unsigned char message[BLAKE2B_BLOCK_SIZE]; 237 | 238 | /* calculate the total input capacities of tx */ 239 | while (1) { 240 | uint64_t capacity = 0; 241 | len = 8; 242 | ret = ckb_load_cell_by_field(((unsigned char *)&capacity), &len, 0, index, 243 | CKB_SOURCE_INPUT, CKB_CELL_FIELD_CAPACITY); 244 | 245 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 246 | break; 247 | } 248 | if (ret != CKB_SUCCESS) { 249 | return ret; 250 | } 251 | if (len != 8) { 252 | return ERROR_SYSCALL; 253 | } 254 | 255 | if (__builtin_uaddl_overflow(input_capacities, capacity, 256 | &input_capacities)) { 257 | return ERROR_OVERFLOW; 258 | } 259 | 260 | index += 1; 261 | } 262 | 263 | index = 0; 264 | 265 | SHA3_CTX sha3_ctx, sha3_ctx_output; 266 | keccak_init(&sha3_ctx); 267 | 268 | /* calculate the total output capacities and OUTPUT hash value */ 269 | while (1) { 270 | uint64_t capacity = 0; 271 | len = 8; 272 | ret = ckb_load_cell_by_field(((unsigned char *)&capacity), &len, 0, index, 273 | CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_CAPACITY); 274 | 275 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 276 | // return ret; 277 | break; 278 | } 279 | if (ret != CKB_SUCCESS) { 280 | return ret; 281 | } 282 | if (len != 8) { 283 | return ERROR_SYSCALL; 284 | } 285 | if (index >= MAX_OUTPUT_LENGTH) { 286 | return ERROR_TOO_MANY_OUTPUT_CELLS; 287 | } 288 | 289 | if (__builtin_uaddl_overflow(output_capacities, capacity, 290 | &output_capacities)) { 291 | return ERROR_OVERFLOW; 292 | } 293 | 294 | len = SCRIPT_SIZE; 295 | ret = ckb_load_cell_by_field(script, &len, 0, index, CKB_SOURCE_OUTPUT, 296 | CKB_CELL_FIELD_LOCK); 297 | if (ret != CKB_SUCCESS) { 298 | return ERROR_SYSCALL; 299 | } 300 | if (len > SCRIPT_SIZE) { 301 | return ERROR_SCRIPT_TOO_LONG; 302 | } 303 | script_seg.ptr = (uint8_t *)script; 304 | script_seg.size = len; 305 | 306 | if (MolReader_Script_verify(&script_seg, false) != MOL_OK) { 307 | return ERROR_ENCODING; 308 | } 309 | 310 | hash_amount(capacity, amount_hash); 311 | ret = hash_address(&script_seg, address_hash); 312 | if (ret != CKB_SUCCESS) return ret; 313 | 314 | keccak_init(&sha3_ctx_output); 315 | keccak_update(&sha3_ctx_output, OUTPUT_TYPEHASH, 32); 316 | 317 | keccak_update(&sha3_ctx_output, address_hash, 32); 318 | keccak_update(&sha3_ctx_output, amount_hash, 32); 319 | 320 | keccak_final(&sha3_ctx_output, message); 321 | 322 | /* output hash */ 323 | keccak_update(&sha3_ctx, message, 32); 324 | index += 1; 325 | } 326 | 327 | /* 328 | * Calcuate tx fee. 329 | * Notice: For dao withdraw transcation, the calculated fee may be negative, 330 | * we set the tx fee to zero. The rules for dao withdraw tx should be 331 | * followed, when build typed data at other places. 332 | */ 333 | if (__builtin_usubl_overflow(input_capacities, output_capacities, &tx_fee)) { 334 | // return ERROR_OVERFLOW; 335 | tx_fee = 0; 336 | } 337 | 338 | /* output array hash */ 339 | keccak_final(&sha3_ctx, message); 340 | /* ckb tx value hash */ 341 | keccak_init(&sha3_ctx); 342 | keccak_update(&sha3_ctx, CKBTRANSACTION_TYPEHASH, 32); 343 | /* hash */ 344 | keccak_update(&sha3_ctx, tx_message, 32); 345 | /* fee */ 346 | hash_amount(tx_fee, amount_hash); 347 | keccak_update(&sha3_ctx, amount_hash, 32); 348 | /* input-sum */ 349 | hash_amount(input_capacities, amount_hash); 350 | keccak_update(&sha3_ctx, amount_hash, 32); 351 | /* to */ 352 | keccak_update(&sha3_ctx, message, 32); 353 | keccak_final(&sha3_ctx, message); 354 | 355 | /* typed data hash */ 356 | keccak_init(&sha3_ctx); 357 | keccak_update(&sha3_ctx, TYPEDDATA_PREFIX, 2); 358 | keccak_update(&sha3_ctx, DOMAIN_SEPARATOR, 32); 359 | keccak_update(&sha3_ctx, message, 32); 360 | keccak_final(&sha3_ctx, typed_data_hash); 361 | 362 | return CKB_SUCCESS; 363 | } 364 | 365 | /* 366 | * Verify secp256k1 signature, check the kecack256 hash of pubkey recovered from 367 | * signature is equal with the lock script arg. 368 | * 369 | * Arguments: 370 | * message, the calculated hash of ckb transaciton. 371 | * lock_bytes, the signature from transcation witness. 372 | * lock_args, the args of lock script. 373 | * 374 | * 375 | */ 376 | int verify_signature(unsigned char *message, unsigned char *lock_bytes, 377 | const void *lock_args) { 378 | unsigned char temp[TEMP_SIZE]; 379 | 380 | /* Load signature */ 381 | secp256k1_context context; 382 | uint8_t secp_data[CKB_SECP256K1_DATA_SIZE]; 383 | int ret = ckb_secp256k1_custom_verify_only_initialize(&context, secp_data); 384 | if (ret != 0) { 385 | return ret; 386 | } 387 | 388 | secp256k1_ecdsa_recoverable_signature signature; 389 | if (secp256k1_ecdsa_recoverable_signature_parse_compact( 390 | &context, &signature, lock_bytes, lock_bytes[RECID_INDEX]) == 0) { 391 | return ERROR_SECP_PARSE_SIGNATURE; 392 | } 393 | 394 | /* Recover pubkey */ 395 | secp256k1_pubkey pubkey; 396 | if (secp256k1_ecdsa_recover(&context, &pubkey, &signature, message) != 1) { 397 | return ERROR_SECP_RECOVER_PUBKEY; 398 | } 399 | 400 | /* Check pubkey hash */ 401 | size_t pubkey_size = PUBKEY_SIZE; 402 | if (secp256k1_ec_pubkey_serialize(&context, temp, &pubkey_size, &pubkey, 403 | SECP256K1_EC_UNCOMPRESSED) != 1) { 404 | return ERROR_SECP_SERIALIZE_PUBKEY; 405 | } 406 | 407 | SHA3_CTX sha3_ctx; 408 | keccak_init(&sha3_ctx); 409 | keccak_update(&sha3_ctx, &temp[1], pubkey_size - 1); 410 | keccak_final(&sha3_ctx, temp); 411 | 412 | if (memcmp(lock_args, &temp[12], BLAKE160_SIZE) != 0) { 413 | return ERROR_PUBKEY_BLAKE160_HASH; 414 | } 415 | 416 | return CKB_SUCCESS; 417 | } 418 | 419 | /* 420 | * Verify the transaction using secp256k1 as sig algorithm and keccak256 as hash 421 | * algorithm. 422 | * 423 | * Since not all ethereum wallets support EIP712, the verification will support 424 | * two hashes for transaction, both of them are ok. 425 | * 1. ethereum peronsal hash 426 | * 2. EIP712 typed data hash 427 | * 428 | * Arguments: 429 | * eth_address, keccak256 hash of pubkey last 20 bytes, used to shield the real 430 | * pubkey. 431 | * 432 | * Witness: 433 | * WitnessArgs with a signature in lock field used to present ownership. 434 | */ 435 | int verify_secp256k1_keccak_sighash_all( 436 | unsigned char eth_address[BLAKE160_SIZE]) { 437 | int ret; 438 | uint64_t len = 0; 439 | unsigned char temp[TEMP_SIZE]; 440 | unsigned char lock_bytes[SIGNATURE_SIZE]; 441 | 442 | /* Load witness of first input */ 443 | uint64_t witness_len = MAX_WITNESS_SIZE; 444 | ret = ckb_load_witness(temp, &witness_len, 0, 0, CKB_SOURCE_GROUP_INPUT); 445 | if (ret != CKB_SUCCESS) { 446 | return ERROR_SYSCALL; 447 | } 448 | 449 | if (witness_len > MAX_WITNESS_SIZE) { 450 | return ERROR_WITNESS_SIZE; 451 | } 452 | 453 | /* load signature */ 454 | mol_seg_t lock_bytes_seg; 455 | ret = extract_witness_lock(temp, witness_len, &lock_bytes_seg); 456 | if (ret != 0) { 457 | return ERROR_ENCODING; 458 | } 459 | 460 | if (lock_bytes_seg.size != SIGNATURE_SIZE) { 461 | return ERROR_ARGUMENTS_LEN; 462 | } 463 | memcpy(lock_bytes, lock_bytes_seg.ptr, lock_bytes_seg.size); 464 | 465 | /* Load tx hash */ 466 | unsigned char tx_hash[BLAKE2B_BLOCK_SIZE]; 467 | len = BLAKE2B_BLOCK_SIZE; 468 | ret = ckb_load_tx_hash(tx_hash, &len, 0); 469 | if (ret != CKB_SUCCESS) { 470 | return ret; 471 | } 472 | if (len != BLAKE2B_BLOCK_SIZE) { 473 | return ERROR_SYSCALL; 474 | } 475 | 476 | /* Prepare sign message */ 477 | unsigned char message[BLAKE2B_BLOCK_SIZE]; 478 | SHA3_CTX sha3_ctx; 479 | keccak_init(&sha3_ctx); 480 | keccak_update(&sha3_ctx, tx_hash, BLAKE2B_BLOCK_SIZE); 481 | 482 | /* Clear lock field to zero, then digest the first witness */ 483 | memset((void *)lock_bytes_seg.ptr, 0, lock_bytes_seg.size); 484 | keccak_update(&sha3_ctx, (unsigned char *)&witness_len, sizeof(uint64_t)); 485 | keccak_update(&sha3_ctx, temp, witness_len); 486 | 487 | /* Digest same group witnesses */ 488 | size_t i = 1; 489 | while (1) { 490 | len = MAX_WITNESS_SIZE; 491 | ret = ckb_load_witness(temp, &len, 0, i, CKB_SOURCE_GROUP_INPUT); 492 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 493 | break; 494 | } 495 | if (ret != CKB_SUCCESS) { 496 | return ERROR_SYSCALL; 497 | } 498 | if (len > MAX_WITNESS_SIZE) { 499 | return ERROR_WITNESS_SIZE; 500 | } 501 | keccak_update(&sha3_ctx, (unsigned char *)&len, sizeof(uint64_t)); 502 | keccak_update(&sha3_ctx, temp, len); 503 | i += 1; 504 | } 505 | /* Digest witnesses that not covered by inputs */ 506 | i = calculate_inputs_len(); 507 | while (1) { 508 | len = MAX_WITNESS_SIZE; 509 | ret = ckb_load_witness(temp, &len, 0, i, CKB_SOURCE_INPUT); 510 | if (ret == CKB_INDEX_OUT_OF_BOUND) { 511 | break; 512 | } 513 | if (ret != CKB_SUCCESS) { 514 | return ERROR_SYSCALL; 515 | } 516 | if (len > MAX_WITNESS_SIZE) { 517 | return ERROR_WITNESS_SIZE; 518 | } 519 | keccak_update(&sha3_ctx, (unsigned char *)&len, sizeof(uint64_t)); 520 | keccak_update(&sha3_ctx, temp, len); 521 | 522 | i += 1; 523 | } 524 | keccak_final(&sha3_ctx, message); 525 | 526 | keccak_init(&sha3_ctx); 527 | /* personal hash, ethereum prefix \u0019Ethereum Signed Message:\n32 */ 528 | unsigned char eth_prefix[28] = {0x19, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 529 | 0x75, 0x6d, 0x20, 0x53, 0x69, 0x67, 0x6e, 530 | 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 531 | 0x61, 0x67, 0x65, 0x3a, 0x0a, 0x33, 0x32}; 532 | keccak_update(&sha3_ctx, eth_prefix, 28); 533 | keccak_update(&sha3_ctx, message, 32); 534 | keccak_final(&sha3_ctx, message); 535 | 536 | /* verify signature with peronsal hash */ 537 | ret = verify_signature(message, lock_bytes, eth_address); 538 | if (ret == CKB_SUCCESS) { 539 | return CKB_SUCCESS; 540 | } 541 | 542 | /* Calculate Typed Data hash */ 543 | ret = calculate_typed_data(message, message); 544 | if (ret != CKB_SUCCESS) { 545 | return ret; 546 | } 547 | 548 | /* verify signature with typed data hash */ 549 | return verify_signature(message, lock_bytes, eth_address); 550 | } 551 | -------------------------------------------------------------------------------- /c/secp256k1_keccak256_sighash_all.c: -------------------------------------------------------------------------------- 1 | /* The script perform secp256k1_keccak256_sighash_all verification. */ 2 | #include "ckb_syscalls.h" 3 | #include "protocol.h" 4 | #include "secp256k1_helper.h" 5 | #include "secp256k1_keccak256_lock.h" 6 | 7 | #define BLAKE2B_BLOCK_SIZE 32 8 | #define BLAKE160_SIZE 20 9 | #define PUBKEY_SIZE 65 // ETH address uncompress pub key 10 | #define TEMP_SIZE 32768 11 | #define RECID_INDEX 64 12 | /* 32 KB */ 13 | #define MAX_WITNESS_SIZE 32768 14 | #define SCRIPT_SIZE 32768 15 | #define SIGNATURE_SIZE 65 16 | 17 | #define MAX_OUTPUT_LENGTH 64 18 | 19 | #define ERROR_TOO_MANY_OUTPUT_CELLS -18 20 | 21 | #if (MAX_WITNESS_SIZE > TEMP_SIZE) || (SCRIPT_SIZE > TEMP_SIZE) 22 | #error "Temp buffer is not big enough!" 23 | #endif 24 | 25 | /* 26 | * Arguments: 27 | * ethereum address, keccak256 hash of pubkey last 20 bytes, used to 28 | * shield the real pubkey. 29 | * 30 | * Witness: 31 | * WitnessArgs with a signature in lock field used to present ownership. 32 | */ 33 | int main() { 34 | int ret; 35 | uint64_t len = 0; 36 | 37 | /* Load args */ 38 | unsigned char script[SCRIPT_SIZE]; 39 | len = SCRIPT_SIZE; 40 | ret = ckb_load_script(script, &len, 0); 41 | if (ret != CKB_SUCCESS) { 42 | return ERROR_SYSCALL; 43 | } 44 | if (len > SCRIPT_SIZE) { 45 | return ERROR_SCRIPT_TOO_LONG; 46 | } 47 | mol_seg_t script_seg; 48 | script_seg.ptr = (uint8_t *)script; 49 | script_seg.size = len; 50 | 51 | if (MolReader_Script_verify(&script_seg, false) != MOL_OK) { 52 | return ERROR_ENCODING; 53 | } 54 | 55 | mol_seg_t args_seg = MolReader_Script_get_args(&script_seg); 56 | mol_seg_t args_bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); 57 | if (args_bytes_seg.size != BLAKE160_SIZE) { 58 | return ERROR_ARGUMENTS_LEN; 59 | } 60 | 61 | return verify_secp256k1_keccak_sighash_all(args_bytes_seg.ptr); 62 | } 63 | -------------------------------------------------------------------------------- /c/secp256k1_keccak256_sighash_all_acpl.c: -------------------------------------------------------------------------------- 1 | /* UDT anyone-can-pay lock script 2 | * For simplify, we call a cell with anyone-can-pay lock a wallet cell. 3 | * 4 | * Wallet cell can be unlocked without a signature, if: 5 | * 6 | * 1. There is 1 output wallet cell that has the same type hash with the 7 | * unlocked wallet cell. 8 | * 2. The UDT or CKB(if type script is none) in the output wallet is more than 9 | * the unlocked wallet. 10 | * 3. if the type script is none, the cell data is empty. 11 | * 12 | * otherwise, the script perform secp256k1_keccak256_sighash_all verification. 13 | */ 14 | 15 | #include "ckb_syscalls.h" 16 | #include "defs.h" 17 | #include "protocol.h" 18 | #include "quick_pow10.h" 19 | #include "secp256k1_helper.h" 20 | #include "secp256k1_keccak256_lock.h" 21 | #include "anyone_can_pay_lock.h" 22 | 23 | #define BLAKE2B_BLOCK_SIZE 32 24 | #define BLAKE160_SIZE 20 25 | #define PUBKEY_SIZE 65 // ETH address uncompress pub key 26 | #define TEMP_SIZE 32768 27 | #define RECID_INDEX 64 28 | /* 32 KB */ 29 | #define MAX_WITNESS_SIZE 32768 30 | #define SCRIPT_SIZE 32768 31 | #define SIGNATURE_SIZE 65 32 | 33 | #define MAX_OUTPUT_LENGTH 64 34 | 35 | #define ERROR_TOO_MANY_OUTPUT_CELLS -18 36 | 37 | #if (MAX_WITNESS_SIZE > TEMP_SIZE) || (SCRIPT_SIZE > TEMP_SIZE) 38 | #error "Temp buffer is not big enough!" 39 | #endif 40 | 41 | int has_signature(int *has_sig) { 42 | int ret; 43 | unsigned char temp[MAX_WITNESS_SIZE]; 44 | 45 | /* Load witness of first input */ 46 | uint64_t witness_len = MAX_WITNESS_SIZE; 47 | ret = ckb_load_witness(temp, &witness_len, 0, 0, CKB_SOURCE_GROUP_INPUT); 48 | 49 | if ((ret == CKB_INDEX_OUT_OF_BOUND) || 50 | (ret == CKB_SUCCESS && witness_len == 0)) { 51 | *has_sig = 0; 52 | return CKB_SUCCESS; 53 | } 54 | 55 | if (ret != CKB_SUCCESS) { 56 | return ERROR_SYSCALL; 57 | } 58 | 59 | if (witness_len > MAX_WITNESS_SIZE) { 60 | return ERROR_WITNESS_SIZE; 61 | } 62 | 63 | /* load signature */ 64 | mol_seg_t lock_bytes_seg; 65 | ret = extract_witness_lock(temp, witness_len, &lock_bytes_seg); 66 | if (ret != 0) { 67 | return ERROR_ENCODING; 68 | } 69 | 70 | *has_sig = lock_bytes_seg.size > 0; 71 | return CKB_SUCCESS; 72 | } 73 | 74 | int read_args(unsigned char *pubkey_hash, uint64_t *min_ckb_amount, 75 | uint128_t *min_udt_amount) { 76 | int ret; 77 | uint64_t len = 0; 78 | 79 | /* Load args */ 80 | unsigned char script[SCRIPT_SIZE]; 81 | len = SCRIPT_SIZE; 82 | ret = ckb_load_script(script, &len, 0); 83 | if (ret != CKB_SUCCESS) { 84 | return ERROR_SYSCALL; 85 | } 86 | if (len > SCRIPT_SIZE) { 87 | return ERROR_SCRIPT_TOO_LONG; 88 | } 89 | mol_seg_t script_seg; 90 | script_seg.ptr = (uint8_t *)script; 91 | script_seg.size = len; 92 | 93 | if (MolReader_Script_verify(&script_seg, false) != MOL_OK) { 94 | return ERROR_ENCODING; 95 | } 96 | 97 | mol_seg_t args_seg = MolReader_Script_get_args(&script_seg); 98 | mol_seg_t args_bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); 99 | if (args_bytes_seg.size < BLAKE160_SIZE || 100 | args_bytes_seg.size > BLAKE160_SIZE + 2) { 101 | return ERROR_ARGUMENTS_LEN; 102 | } 103 | memcpy(pubkey_hash, args_bytes_seg.ptr, BLAKE160_SIZE); 104 | *min_ckb_amount = 0; 105 | *min_udt_amount = 0; 106 | if (args_bytes_seg.size > BLAKE160_SIZE) { 107 | int x = args_bytes_seg.ptr[BLAKE160_SIZE]; 108 | int is_overflow = quick_pow10(x, min_ckb_amount); 109 | if (is_overflow) { 110 | *min_ckb_amount = MAX_UINT64; 111 | } 112 | } 113 | if (args_bytes_seg.size > BLAKE160_SIZE + 1) { 114 | int x = args_bytes_seg.ptr[BLAKE160_SIZE + 1]; 115 | int is_overflow = uint128_quick_pow10(x, min_udt_amount); 116 | if (is_overflow) { 117 | *min_udt_amount = MAX_UINT128; 118 | } 119 | } 120 | return CKB_SUCCESS; 121 | } 122 | 123 | /* 124 | * Arguments: 125 | * ethereum address, keccak256 hash of pubkey last 20 bytes, used to 126 | * shield the real pubkey. 127 | * 128 | * Witness: 129 | * WitnessArgs with a signature in lock field used to present ownership. 130 | */ 131 | int main() { 132 | int ret; 133 | int has_sig; 134 | unsigned char pubkey_hash[BLAKE160_SIZE]; 135 | uint64_t min_ckb_amount; 136 | uint128_t min_udt_amount; 137 | ret = read_args(pubkey_hash, &min_ckb_amount, &min_udt_amount); 138 | if (ret != CKB_SUCCESS) { 139 | return ret; 140 | } 141 | ret = has_signature(&has_sig); 142 | if (ret != CKB_SUCCESS) { 143 | return ret; 144 | } 145 | if (has_sig) { 146 | /* unlock via signature */ 147 | return verify_secp256k1_keccak_sighash_all(pubkey_hash); 148 | } else { 149 | /* unlock via payment */ 150 | return check_payment_unlock(min_ckb_amount, min_udt_amount); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /c/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | utils.h 3 | 4 | Defines basic utility functions. 5 | */ 6 | 7 | #ifndef CKB_UTILS_H_ 8 | #define CKB_UTILS_H_ 9 | 10 | /* a and b are since value, 11 | return 0 if a is equals to b, 12 | return -1 if a is less than b, 13 | return 1 if a is greater than b */ 14 | int epoch_number_with_fraction_cmp(uint64_t a, uint64_t b) { 15 | static const size_t NUMBER_OFFSET = 0; 16 | static const size_t NUMBER_BITS = 24; 17 | static const uint64_t NUMBER_MAXIMUM_VALUE = (1 << NUMBER_BITS); 18 | static const uint64_t NUMBER_MASK = (NUMBER_MAXIMUM_VALUE - 1); 19 | static const size_t INDEX_OFFSET = NUMBER_BITS; 20 | static const size_t INDEX_BITS = 16; 21 | static const uint64_t INDEX_MAXIMUM_VALUE = (1 << INDEX_BITS); 22 | static const uint64_t INDEX_MASK = (INDEX_MAXIMUM_VALUE - 1); 23 | static const size_t LENGTH_OFFSET = NUMBER_BITS + INDEX_BITS; 24 | static const size_t LENGTH_BITS = 16; 25 | static const uint64_t LENGTH_MAXIMUM_VALUE = (1 << LENGTH_BITS); 26 | static const uint64_t LENGTH_MASK = (LENGTH_MAXIMUM_VALUE - 1); 27 | 28 | /* extract a epoch */ 29 | uint64_t a_epoch = (a >> NUMBER_OFFSET) & NUMBER_MASK; 30 | uint64_t a_index = (a >> INDEX_OFFSET) & INDEX_MASK; 31 | uint64_t a_len = (a >> LENGTH_OFFSET) & LENGTH_MASK; 32 | 33 | /* extract b epoch */ 34 | uint64_t b_epoch = (b >> NUMBER_OFFSET) & NUMBER_MASK; 35 | uint64_t b_index = (b >> INDEX_OFFSET) & INDEX_MASK; 36 | uint64_t b_len = (b >> LENGTH_OFFSET) & LENGTH_MASK; 37 | 38 | if (a_epoch < b_epoch) { 39 | return -1; 40 | } else if (a_epoch > b_epoch) { 41 | return 1; 42 | } else { 43 | /* a and b is in the same epoch, 44 | compare a_index / a_len <=> b_index / b_len 45 | */ 46 | uint64_t a_block = a_index * b_len; 47 | uint64_t b_block = b_index * a_len; 48 | /* compare block */ 49 | if (a_block < b_block) { 50 | return -1; 51 | } else if (a_block > b_block) { 52 | return 1; 53 | } else { 54 | return 0; 55 | } 56 | } 57 | } 58 | 59 | #endif /* CKB_UTILS_H_ */ 60 | -------------------------------------------------------------------------------- /deps/molecule/VERSION: -------------------------------------------------------------------------------- 1 | 0.4.1 2 | -------------------------------------------------------------------------------- /deps/molecule/molecule_builder.h: -------------------------------------------------------------------------------- 1 | #ifndef MOLECULE_BUILDER_H 2 | #define MOLECULE_BUILDER_H 3 | 4 | #ifdef __cplusplus 5 | #define _CPP_BEGIN extern "C" { 6 | #define _CPP_END } 7 | _CPP_BEGIN 8 | #endif /* __cplusplus */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "molecule_reader.h" 15 | 16 | /* 17 | * This part is not for normal users. 18 | */ 19 | 20 | // Test if the host is big endian machine. 21 | #define is_le() (*(unsigned char *)&(uint16_t){1}) 22 | 23 | /* 24 | * Definitions of types and simple utilities. 25 | */ 26 | 27 | // The Builder. 28 | // - Can be stack allocated 29 | // - Must be initialized with `MolBuilder_Xxx_init` 30 | // - Must be cleared with `MolBuilder_Xxx_build` or `MolBuilder_Xxx_clear` 31 | // - Can be set with: 32 | // - `MolBuilder_Xxx_set` (For Option) 33 | // - `MolBuilder_Xxx_set_*` (For Union, Array, Struct, Table) 34 | // - `MolBuilder_Xxx_push` (For FixVec, DynVec) 35 | typedef struct { 36 | uint8_t *data_ptr; // Data Pointer 37 | mol_num_t data_used; // Data Used 38 | mol_num_t data_cap; // Data Capacity 39 | 40 | mol_num_t *number_ptr; // A Pointer of Numbers 41 | mol_num_t number_used; // Numbers used 42 | mol_num_t number_cap; // Numbers Capacity 43 | } mol_builder_t; 44 | 45 | /* Utilities. */ 46 | 47 | void mol_pack_number(uint8_t *dst, mol_num_t *num) { 48 | const uint8_t *src = (const uint8_t *)num; 49 | if (is_le()) { 50 | memcpy(dst, src, MOL_NUM_T_SIZE); 51 | } else { 52 | dst[3] = src[0]; 53 | dst[2] = src[1]; 54 | dst[1] = src[2]; 55 | dst[0] = src[3]; 56 | } 57 | } 58 | 59 | /* 60 | * Core functions. 61 | */ 62 | 63 | void mol_builder_discard(mol_builder_t builder) { 64 | free(builder.data_ptr); 65 | free(builder.number_ptr); 66 | } 67 | 68 | void mol_builder_initialize_fixed_size(mol_builder_t *builder, mol_num_t fixed_size) { 69 | if (fixed_size == 0) { 70 | builder->data_ptr = NULL; 71 | builder->data_used = 0; 72 | builder->data_cap = 0; 73 | } else { 74 | builder->data_ptr = (uint8_t*)malloc(fixed_size); 75 | memset(builder->data_ptr, 0x00, fixed_size); 76 | builder->data_used = fixed_size; 77 | builder->data_cap = fixed_size; 78 | } 79 | builder->number_ptr = NULL; 80 | builder->number_used = 0; 81 | builder->number_cap = 0; 82 | } 83 | 84 | void mol_union_builder_initialize(mol_builder_t *builder, mol_num_t data_capacity, mol_num_t item_id, const uint8_t *default_ptr, mol_num_t default_len) { 85 | builder->data_ptr = (uint8_t*)malloc(data_capacity); 86 | builder->data_cap = data_capacity; 87 | mol_pack_number(builder->data_ptr, &item_id); 88 | builder->data_used = MOL_NUM_T_SIZE + default_len; 89 | if (default_ptr == NULL) { 90 | *(builder->data_ptr+MOL_NUM_T_SIZE) = 0; 91 | } else { 92 | memcpy(builder->data_ptr+MOL_NUM_T_SIZE, default_ptr, default_len); 93 | } 94 | builder->number_ptr = NULL; 95 | builder->number_used = 0; 96 | builder->number_cap = 0; 97 | } 98 | 99 | void mol_builder_initialize_with_capacity(mol_builder_t *builder, mol_num_t data_capacity, mol_num_t number_capacity) { 100 | builder->data_ptr = (uint8_t*)malloc(data_capacity); 101 | builder->data_used = 0; 102 | builder->data_cap = data_capacity; 103 | builder->number_ptr = (mol_num_t*)malloc(number_capacity); 104 | builder->number_used = 0; 105 | builder->number_cap = number_capacity; 106 | } 107 | 108 | void mol_fixvec_builder_initialize(mol_builder_t *builder, mol_num_t data_capacity) { 109 | mol_builder_initialize_with_capacity(builder, data_capacity, MOL_NUM_T_SIZE); 110 | builder->number_ptr[0] = 0; 111 | builder->number_used = MOL_NUM_T_SIZE; 112 | } 113 | 114 | void mol_table_builder_initialize(mol_builder_t *builder, mol_num_t data_capacity, mol_num_t field_count) { 115 | mol_builder_initialize_with_capacity(builder, data_capacity, MOL_NUM_T_SIZE * field_count * 2); 116 | memset(builder->number_ptr, 0x00, builder->number_cap); 117 | builder->number_used = builder->number_cap; 118 | } 119 | 120 | void mol_option_builder_set(mol_builder_t *builder, const uint8_t *data_ptr, mol_num_t data_len) { 121 | builder->data_used = data_len; 122 | if (builder->data_used == 0) { 123 | builder->data_cap = 0; 124 | free(builder->data_ptr); 125 | builder->data_ptr = NULL; 126 | } else { 127 | if (builder->data_cap < builder->data_used) { 128 | builder->data_cap = builder->data_used; 129 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 130 | } 131 | memcpy(builder->data_ptr, data_ptr, builder->data_used); 132 | } 133 | } 134 | 135 | void mol_union_builder_set_byte(mol_builder_t *builder, mol_num_t item_id, uint8_t data) { 136 | builder->data_used = MOL_NUM_T_SIZE + 1; 137 | if (builder->data_cap < builder->data_used) { 138 | builder->data_cap = builder->data_used; 139 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 140 | } 141 | mol_pack_number(builder->data_ptr, &item_id); 142 | *(builder->data_ptr+MOL_NUM_T_SIZE) = data; 143 | } 144 | 145 | void mol_union_builder_set(mol_builder_t *builder, mol_num_t item_id, const uint8_t *data_ptr, mol_num_t data_len) { 146 | builder->data_used = MOL_NUM_T_SIZE + data_len; 147 | if (builder->data_cap < builder->data_used) { 148 | builder->data_cap = builder->data_used; 149 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 150 | } 151 | mol_pack_number(builder->data_ptr, &item_id); 152 | memcpy(builder->data_ptr+MOL_NUM_T_SIZE, data_ptr, data_len); 153 | } 154 | 155 | void mol_builder_set_byte_by_offset(mol_builder_t *builder, mol_num_t offset, uint8_t data) { 156 | *(builder->data_ptr+offset) = data; 157 | } 158 | 159 | void mol_builder_set_by_offset(mol_builder_t *builder, mol_num_t offset, const uint8_t *data_ptr, mol_num_t length) { 160 | memcpy(builder->data_ptr+offset, data_ptr, length); 161 | } 162 | 163 | void mol_fixvec_builder_push_byte(mol_builder_t *builder, uint8_t data) { 164 | while (builder->data_cap < builder->data_used + 1) { 165 | builder->data_cap *= 2; 166 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 167 | } 168 | builder->number_ptr[0] += 1; 169 | *(builder->data_ptr+builder->data_used) = data; 170 | builder->data_used += 1; 171 | } 172 | 173 | void mol_fixvec_builder_push(mol_builder_t *builder, const uint8_t *data_ptr, mol_num_t length) { 174 | while (builder->data_cap < builder->data_used + length) { 175 | builder->data_cap *= 2; 176 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 177 | } 178 | builder->number_ptr[0] += 1; 179 | memcpy(builder->data_ptr+builder->data_used, data_ptr, length); 180 | builder->data_used += length; 181 | } 182 | 183 | void mol_dynvec_builder_push(mol_builder_t *builder, const uint8_t *data_ptr, mol_num_t data_len) { 184 | while (builder->data_cap < builder->data_used + data_len) { 185 | builder->data_cap *= 2; 186 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 187 | } 188 | while (builder->number_cap < builder->number_used + MOL_NUM_T_SIZE) { 189 | builder->number_cap *= 2; 190 | builder->number_ptr = (mol_num_t*)realloc(builder->number_ptr, builder->number_cap); 191 | } 192 | 193 | mol_num_t next_number_index = builder->number_used / MOL_NUM_T_SIZE; 194 | builder->number_ptr[next_number_index] = builder->data_used; 195 | builder->number_used += MOL_NUM_T_SIZE; 196 | 197 | if (data_len != 0) { 198 | memcpy(builder->data_ptr+builder->data_used, data_ptr, data_len); 199 | builder->data_used += data_len; 200 | } 201 | } 202 | 203 | void mol_table_builder_add_byte(mol_builder_t *builder, mol_num_t field_index, uint8_t data) { 204 | while (builder->data_cap < builder->data_used + 1) { 205 | builder->data_cap *= 2; 206 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 207 | } 208 | 209 | builder->number_ptr[field_index * 2] = builder->data_used; 210 | builder->number_ptr[field_index * 2 + 1] = 1; 211 | *(builder->data_ptr+builder->data_used) = data; 212 | builder->data_used += 1; 213 | } 214 | 215 | void mol_table_builder_add(mol_builder_t *builder, mol_num_t field_index, const uint8_t *data_ptr, mol_num_t data_len) { 216 | if (data_len == 0) { 217 | builder->number_ptr[field_index * 2] = 0; 218 | builder->number_ptr[field_index * 2 + 1] = 0; 219 | } else { 220 | while (builder->data_cap < builder->data_used + data_len) { 221 | builder->data_cap *= 2; 222 | builder->data_ptr = (uint8_t*)realloc(builder->data_ptr, builder->data_cap); 223 | } 224 | 225 | builder->number_ptr[field_index * 2] = builder->data_used; 226 | builder->number_ptr[field_index * 2 + 1] = data_len; 227 | memcpy(builder->data_ptr+builder->data_used, data_ptr, data_len); 228 | builder->data_used += data_len; 229 | } 230 | } 231 | 232 | mol_seg_res_t mol_builder_finalize_simple(mol_builder_t builder) { 233 | mol_seg_res_t res; 234 | res.errno = MOL_OK; 235 | res.seg.ptr = builder.data_ptr; 236 | res.seg.size = builder.data_used; 237 | free(builder.number_ptr); 238 | return res; 239 | } 240 | 241 | mol_seg_res_t mol_fixvec_builder_finalize(mol_builder_t builder) { 242 | mol_seg_res_t res; 243 | res.errno = MOL_OK; 244 | res.seg.size = MOL_NUM_T_SIZE + builder.data_used; 245 | res.seg.ptr = (uint8_t*)malloc(res.seg.size); 246 | mol_pack_number(res.seg.ptr, &builder.number_ptr[0]); 247 | if (builder.data_used > 0) { 248 | memcpy((res.seg.ptr+MOL_NUM_T_SIZE), builder.data_ptr, builder.data_used); 249 | } 250 | mol_builder_discard(builder); 251 | return res; 252 | } 253 | 254 | mol_seg_res_t mol_dynvec_builder_finalize(mol_builder_t builder) { 255 | mol_seg_res_t res; 256 | res.errno = MOL_OK; 257 | res.seg.size = MOL_NUM_T_SIZE + builder.number_used + builder.data_used; 258 | res.seg.ptr = (uint8_t*)malloc(res.seg.size); 259 | mol_pack_number(res.seg.ptr, &res.seg.size); 260 | if (builder.data_used > 0) { 261 | mol_num_t number_count = builder.number_used / MOL_NUM_T_SIZE; 262 | mol_num_t header_size = MOL_NUM_T_SIZE + builder.number_used; 263 | for (mol_num_t number_index=0; number_index 11 | #include 12 | 13 | /* 14 | * This part is not for normal users. 15 | */ 16 | 17 | // Test if the host is big endian machine. 18 | #define is_le() (*(unsigned char *)&(uint16_t){1}) 19 | 20 | /* 21 | * Definitions of types and simple utilities. 22 | */ 23 | 24 | /* Core types */ 25 | 26 | typedef uint32_t mol_num_t; // Item Id 27 | 28 | typedef uint8_t mol_errno; // Error Number 29 | 30 | #define MolNum UINT32_C 31 | 32 | #define MOL_NUM_T_SIZE 4 33 | 34 | // Bytes segment. 35 | typedef struct { 36 | uint8_t *ptr; // Pointer 37 | mol_num_t size; // Full size 38 | } mol_seg_t; 39 | 40 | // Unpacked Union 41 | typedef struct { 42 | mol_num_t item_id; // Item Id 43 | mol_seg_t seg; // Segment 44 | } mol_union_t; 45 | 46 | // Result for returning segment. 47 | typedef struct { 48 | mol_errno errno; // Error Number 49 | mol_seg_t seg; // Segment 50 | } mol_seg_res_t; 51 | 52 | /* Error Numbers */ 53 | 54 | #define MOL_OK 0x00 55 | #define MOL_ERR 0xff 56 | 57 | #define MOL_ERR_TOTAL_SIZE 0x01 58 | #define MOL_ERR_HEADER 0x02 59 | #define MOL_ERR_OFFSET 0x03 60 | #define MOL_ERR_UNKNOWN_ITEM 0x04 61 | #define MOL_ERR_INDEX_OUT_OF_BOUNDS 0x05 62 | #define MOL_ERR_FIELD_COUNT 0x06 63 | #define MOL_ERR_DATA 0x07 64 | 65 | /* Utilities. */ 66 | 67 | mol_num_t mol_unpack_number(const uint8_t *src) { 68 | if (is_le()) { 69 | return *(const uint32_t *)src; 70 | } else { 71 | uint32_t output = 0; 72 | uint8_t *dst = (uint8_t*) &output; 73 | dst[3] = src[0]; 74 | dst[2] = src[1]; 75 | dst[1] = src[2]; 76 | dst[0] = src[3]; 77 | return output; 78 | } 79 | } 80 | 81 | 82 | /* 83 | * Core functions. 84 | */ 85 | 86 | /* Verify Functions. */ 87 | 88 | // Verify Array / Struct. 89 | mol_errno mol_verify_fixed_size(const mol_seg_t *input, mol_num_t total_size) { 90 | return input->size == total_size ? MOL_OK : MOL_ERR_TOTAL_SIZE; 91 | } 92 | 93 | // Verify FixVec. 94 | mol_errno mol_fixvec_verify(const mol_seg_t *input, mol_num_t item_size) { 95 | if (input->size < MOL_NUM_T_SIZE) { 96 | return MOL_ERR_HEADER; 97 | } 98 | mol_num_t item_count = mol_unpack_number(input->ptr); 99 | if (item_count == 0) { 100 | return input->size == MOL_NUM_T_SIZE ? MOL_OK : MOL_ERR_TOTAL_SIZE; 101 | } 102 | mol_num_t total_size = MOL_NUM_T_SIZE + item_size * item_count; 103 | return input->size == total_size ? MOL_OK : MOL_ERR_TOTAL_SIZE; 104 | } 105 | 106 | /* Getters. 107 | * 108 | * ### Notice 109 | * 110 | * The input of getters should be checked. 111 | * 112 | * These getters will raise segmentation fault if the input is illegal or 113 | * return an incorrect result. 114 | */ 115 | 116 | // Check if an Option is None. 117 | bool mol_option_is_none(const mol_seg_t *input) { 118 | return input->size == 0; 119 | } 120 | 121 | // Get the inner of a Union. 122 | mol_union_t mol_union_unpack(const mol_seg_t *input) { 123 | mol_union_t ret; 124 | ret.item_id = mol_unpack_number(input->ptr); 125 | ret.seg.ptr = input->ptr + MOL_NUM_T_SIZE; 126 | ret.seg.size = input->size - MOL_NUM_T_SIZE; 127 | return ret; 128 | } 129 | 130 | // Get the length of a FixVec. 131 | mol_num_t mol_fixvec_length(const mol_seg_t *input) { 132 | return mol_unpack_number(input->ptr); 133 | } 134 | 135 | // Get the length of a DynVec. 136 | mol_num_t mol_dynvec_length(const mol_seg_t *input) { 137 | if (input->size == MOL_NUM_T_SIZE) { 138 | return 0; 139 | } else { 140 | return (mol_unpack_number(input->ptr + MOL_NUM_T_SIZE) / 4) - 1; 141 | } 142 | } 143 | 144 | // Get the actual field count of a Table. 145 | mol_num_t mol_table_actual_field_count(const mol_seg_t *input) { 146 | return mol_dynvec_length(input); 147 | } 148 | 149 | // If a Table has extra fields. 150 | bool mol_table_has_extra_fields(const mol_seg_t *input, mol_num_t field_count) { 151 | return mol_table_actual_field_count(input) > field_count; 152 | } 153 | 154 | // Slice a segment for Array / Struct by offset. 155 | mol_seg_t mol_slice_by_offset(const mol_seg_t *input, mol_num_t offset, mol_num_t size) { 156 | mol_seg_t seg; 157 | seg.ptr = input->ptr + offset; 158 | seg.size = size; 159 | return seg; 160 | } 161 | 162 | // Slice a segment for FixVec by index. 163 | mol_seg_res_t mol_fixvec_slice_by_index(const mol_seg_t *input, mol_num_t item_size, mol_num_t item_index) { 164 | mol_seg_res_t res; 165 | mol_num_t item_count = mol_unpack_number(input->ptr); 166 | if (item_index >= item_count) { 167 | res.errno = MOL_ERR_INDEX_OUT_OF_BOUNDS; 168 | } else { 169 | res.errno = MOL_OK; 170 | res.seg.ptr = input->ptr + MOL_NUM_T_SIZE + item_size * item_index; 171 | res.seg.size = item_size; 172 | } 173 | return res; 174 | } 175 | 176 | // Slice a segment for DynVec by index. 177 | mol_seg_res_t mol_dynvec_slice_by_index(const mol_seg_t *input, mol_num_t item_index) { 178 | mol_seg_res_t res; 179 | mol_num_t total_size = mol_unpack_number(input->ptr); 180 | if (total_size == MOL_NUM_T_SIZE) { 181 | res.errno = MOL_ERR_INDEX_OUT_OF_BOUNDS; 182 | } else { 183 | mol_num_t item_count = (mol_unpack_number(input->ptr + MOL_NUM_T_SIZE) / 4) - 1; 184 | if (item_index >= item_count) { 185 | res.errno = MOL_ERR_INDEX_OUT_OF_BOUNDS; 186 | } else { 187 | mol_num_t item_start = mol_unpack_number(input->ptr + MOL_NUM_T_SIZE * (item_index + 1)); 188 | if (item_index + 1 == item_count) { 189 | res.errno = MOL_OK; 190 | res.seg.ptr = input->ptr + item_start; 191 | res.seg.size = total_size - item_start; 192 | } else { 193 | mol_num_t item_end = mol_unpack_number(input->ptr + MOL_NUM_T_SIZE * (item_index + 2)); 194 | res.errno = MOL_OK; 195 | res.seg.ptr = input->ptr + item_start; 196 | res.seg.size = item_end - item_start; 197 | } 198 | } 199 | } 200 | return res; 201 | } 202 | 203 | 204 | // Slice a segment for Table by index. 205 | mol_seg_t mol_table_slice_by_index(const mol_seg_t *input, mol_num_t field_index) { 206 | mol_seg_res_t res = mol_dynvec_slice_by_index(input, field_index); 207 | return res.seg; 208 | } 209 | 210 | // Slice the raw bytes from a `vector ` (FixVec, with a header). 211 | mol_seg_t mol_fixvec_slice_raw_bytes(const mol_seg_t *input) { 212 | mol_seg_t seg; 213 | seg.ptr = input->ptr + MOL_NUM_T_SIZE; 214 | seg.size = mol_unpack_number(input->ptr); 215 | return seg; 216 | } 217 | 218 | /* 219 | * Undef macros which are internal use only. 220 | */ 221 | 222 | #undef is_le 223 | 224 | #ifdef __cplusplus 225 | _CPP_END 226 | #undef _CPP_BEGIN 227 | #undef _CPP_END 228 | #endif /* __cplusplus */ 229 | 230 | #endif /* MOLECULE_READER_H */ 231 | -------------------------------------------------------------------------------- /specs/cells/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lay2dev/pw-lock/9445db0aece263df76255d84c55c3b538b188435/specs/cells/.gitkeep -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! pub use const BUNDLED_CELL: Files 2 | //! pub use const CODE_HASH_DAO: [u8; 32] 3 | //! pub use const CODE_HASH_SECP256K1_BLAKE160_SIGHASH_ALL: [u8; 32] 4 | //! pub use const CODE_HASH_SECP256K1_RIPEMD160_SHA256_SIGHASH_ALL: [u8; 32] 5 | 6 | #![allow(clippy::unreadable_literal)] 7 | 8 | include!(concat!(env!("OUT_DIR"), "/bundled.rs")); 9 | include!(concat!(env!("OUT_DIR"), "/code_hashes.rs")); 10 | 11 | #[cfg(test)] 12 | mod tests; 13 | -------------------------------------------------------------------------------- /src/tests/anyone_can_pay.rs: -------------------------------------------------------------------------------- 1 | use super::{eth160, DummyDataLoader, KECCAK256_ALL_ACPL_BIN, MAX_CYCLES, SECP256K1_DATA_BIN}; 2 | use ckb_crypto::secp::Generator; 3 | use ckb_error::assert_error_eq; 4 | use ckb_script::{ScriptError, TransactionScriptsVerifier}; 5 | use ckb_types::{ 6 | bytes::Bytes, 7 | core::{ 8 | cell::{CellMetaBuilder, ResolvedTransaction}, 9 | Capacity, DepType, ScriptHashType, TransactionBuilder, TransactionView, 10 | }, 11 | packed::{CellDep, CellInput, CellOutput, OutPoint, Script, WitnessArgsBuilder}, 12 | prelude::*, 13 | }; 14 | use rand::{thread_rng, Rng}; 15 | 16 | pub const ERROR_ENCODING: i8 = -2; 17 | pub const ERROR_OVERFLOW: i8 = -41; 18 | pub const ERROR_OUTPUT_AMOUNT_NOT_ENOUGH: i8 = -42; 19 | pub const ERROR_NO_PAIR: i8 = -44; 20 | pub const ERROR_DUPLICATED_INPUTS: i8 = -45; 21 | pub const ERROR_DUPLICATED_OUTPUTS: i8 = -46; 22 | 23 | fn gen_tx(dummy: &mut DummyDataLoader, lock_args: Bytes) -> TransactionView { 24 | let mut rng = thread_rng(); 25 | gen_tx_with_grouped_args(dummy, vec![(lock_args, 1)], &mut rng) 26 | } 27 | 28 | fn gen_tx_with_grouped_args( 29 | dummy: &mut DummyDataLoader, 30 | grouped_args: Vec<(Bytes, usize)>, 31 | rng: &mut R, 32 | ) -> TransactionView { 33 | // setup sighash_all dep 34 | let sighash_all_out_point = { 35 | let contract_tx_hash = { 36 | let mut buf = [0u8; 32]; 37 | rng.fill(&mut buf); 38 | buf.pack() 39 | }; 40 | OutPoint::new(contract_tx_hash.clone(), 0) 41 | }; 42 | // dep contract code 43 | let sighash_all_cell = CellOutput::new_builder() 44 | .capacity( 45 | Capacity::bytes(KECCAK256_ALL_ACPL_BIN.len()) 46 | .expect("script capacity") 47 | .pack(), 48 | ) 49 | .build(); 50 | let sighash_all_cell_data_hash = CellOutput::calc_data_hash(&KECCAK256_ALL_ACPL_BIN); 51 | dummy.cells.insert( 52 | sighash_all_out_point.clone(), 53 | (sighash_all_cell, KECCAK256_ALL_ACPL_BIN.clone()), 54 | ); 55 | // setup secp256k1_data dep 56 | let secp256k1_data_out_point = { 57 | let tx_hash = { 58 | let mut buf = [0u8; 32]; 59 | rng.fill(&mut buf); 60 | buf.pack() 61 | }; 62 | OutPoint::new(tx_hash, 0) 63 | }; 64 | let secp256k1_data_cell = CellOutput::new_builder() 65 | .capacity( 66 | Capacity::bytes(SECP256K1_DATA_BIN.len()) 67 | .expect("data capacity") 68 | .pack(), 69 | ) 70 | .build(); 71 | dummy.cells.insert( 72 | secp256k1_data_out_point.clone(), 73 | (secp256k1_data_cell, SECP256K1_DATA_BIN.clone()), 74 | ); 75 | // setup default tx builder 76 | 77 | let block_assembler_code_hash: [u8; 32] = [ 78 | 0x9b, 0xd7, 0xe0, 0x6f, 0x3e, 0xcf, 0x4b, 0xe0, 0xf2, 0xfc, 0xd2, 0x18, 0x8b, 0x23, 0xf1, 79 | 0xb9, 0xfc, 0xc8, 0x8e, 0x5d, 0x4b, 0x65, 0xa8, 0x63, 0x7b, 0x17, 0x72, 0x3b, 0xbd, 0xa3, 80 | 0xcc, 0xe8, 81 | ]; 82 | let lock_script = Script::new_builder() 83 | .code_hash(block_assembler_code_hash.pack()) 84 | .args([0u8; 33].pack()) 85 | .hash_type(ScriptHashType::Type.into()) 86 | .build(); 87 | let dummy_capacity = Capacity::shannons(42); 88 | let mut tx_builder = TransactionBuilder::default() 89 | .cell_dep( 90 | CellDep::new_builder() 91 | .out_point(sighash_all_out_point) 92 | .dep_type(DepType::Code.into()) 93 | .build(), 94 | ) 95 | .cell_dep( 96 | CellDep::new_builder() 97 | .out_point(secp256k1_data_out_point) 98 | .dep_type(DepType::Code.into()) 99 | .build(), 100 | ) 101 | .output( 102 | CellOutput::new_builder() 103 | .capacity(dummy_capacity.pack()) 104 | .lock(lock_script) 105 | .build(), 106 | ) 107 | .output_data(Bytes::new().pack()); 108 | 109 | for (args, inputs_size) in grouped_args { 110 | // setup dummy input unlock script 111 | for _ in 0..inputs_size { 112 | let previous_tx_hash = { 113 | let mut buf = [0u8; 32]; 114 | rng.fill(&mut buf); 115 | buf.pack() 116 | }; 117 | let previous_out_point = OutPoint::new(previous_tx_hash, 0); 118 | let script = Script::new_builder() 119 | .args(args.pack()) 120 | .code_hash(sighash_all_cell_data_hash.clone()) 121 | .hash_type(ScriptHashType::Data.into()) 122 | .build(); 123 | let previous_output_cell = CellOutput::new_builder() 124 | .capacity(dummy_capacity.pack()) 125 | .lock(script) 126 | .build(); 127 | dummy.cells.insert( 128 | previous_out_point.clone(), 129 | (previous_output_cell.clone(), Bytes::new()), 130 | ); 131 | let mut random_extra_witness = [0u8; 32]; 132 | rng.fill(&mut random_extra_witness); 133 | let witness_args = WitnessArgsBuilder::default() 134 | .extra(Bytes::from(random_extra_witness.to_vec()).pack()) 135 | .build(); 136 | tx_builder = tx_builder 137 | .input(CellInput::new(previous_out_point, 0)) 138 | .witness(witness_args.as_bytes().pack()); 139 | } 140 | } 141 | 142 | tx_builder.build() 143 | } 144 | 145 | fn build_resolved_tx(data_loader: &DummyDataLoader, tx: &TransactionView) -> ResolvedTransaction { 146 | let resolved_cell_deps = tx 147 | .cell_deps() 148 | .into_iter() 149 | .map(|dep| { 150 | let deps_out_point = dep.clone(); 151 | let (dep_output, dep_data) = 152 | data_loader.cells.get(&deps_out_point.out_point()).unwrap(); 153 | CellMetaBuilder::from_cell_output(dep_output.to_owned(), dep_data.to_owned()) 154 | .out_point(deps_out_point.out_point().clone()) 155 | .build() 156 | }) 157 | .collect(); 158 | 159 | let mut resolved_inputs = Vec::new(); 160 | for i in 0..tx.inputs().len() { 161 | let previous_out_point = tx.inputs().get(i).unwrap().previous_output(); 162 | let (input_output, input_data) = data_loader.cells.get(&previous_out_point).unwrap(); 163 | resolved_inputs.push( 164 | CellMetaBuilder::from_cell_output(input_output.to_owned(), input_data.to_owned()) 165 | .out_point(previous_out_point) 166 | .build(), 167 | ); 168 | } 169 | 170 | ResolvedTransaction { 171 | transaction: tx.clone(), 172 | resolved_cell_deps, 173 | resolved_inputs, 174 | resolved_dep_groups: vec![], 175 | } 176 | } 177 | 178 | fn build_anyone_can_pay_script(args: Bytes) -> Script { 179 | let sighash_all_cell_data_hash = CellOutput::calc_data_hash(&KECCAK256_ALL_ACPL_BIN); 180 | Script::new_builder() 181 | .args(args.pack()) 182 | .code_hash(sighash_all_cell_data_hash.clone()) 183 | .hash_type(ScriptHashType::Data.into()) 184 | .build() 185 | } 186 | 187 | #[test] 188 | fn test_unlock_by_anyone() { 189 | let mut data_loader = DummyDataLoader::new(); 190 | let privkey = Generator::random_privkey(); 191 | let pubkey = privkey.pubkey().expect("pubkey"); 192 | let pubkey_hash = eth160(pubkey); 193 | 194 | let script = build_anyone_can_pay_script(pubkey_hash.to_owned()); 195 | let tx = gen_tx(&mut data_loader, pubkey_hash); 196 | let output = tx.outputs().get(0).unwrap(); 197 | let tx = tx 198 | .as_advanced_builder() 199 | .set_witnesses(Vec::new()) 200 | .set_outputs(vec![output 201 | .as_builder() 202 | .lock(script) 203 | .capacity(44u64.pack()) 204 | .build()]) 205 | .build(); 206 | 207 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 208 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 209 | let verify_result = verifier.verify(MAX_CYCLES); 210 | verify_result.expect("pass"); 211 | } 212 | 213 | #[test] 214 | fn test_put_output_data() { 215 | let mut data_loader = DummyDataLoader::new(); 216 | let privkey = Generator::random_privkey(); 217 | let pubkey = privkey.pubkey().expect("pubkey"); 218 | let pubkey_hash = eth160(pubkey); 219 | 220 | let script = build_anyone_can_pay_script(pubkey_hash.to_owned()); 221 | let tx = gen_tx(&mut data_loader, pubkey_hash); 222 | let output = tx.outputs().get(0).unwrap(); 223 | let tx = tx 224 | .as_advanced_builder() 225 | .set_witnesses(Vec::new()) 226 | .set_outputs(vec![output 227 | .as_builder() 228 | .lock(script) 229 | .capacity(44u64.pack()) 230 | .build()]) 231 | .set_outputs_data(vec![Bytes::from(vec![42u8]).pack()]) 232 | .build(); 233 | 234 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 235 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 236 | let verify_result = verifier.verify(MAX_CYCLES); 237 | assert_error_eq!( 238 | verify_result.unwrap_err(), 239 | ScriptError::ValidationFailure(ERROR_ENCODING), 240 | ); 241 | } 242 | 243 | #[test] 244 | fn test_wrong_output_args() { 245 | let mut data_loader = DummyDataLoader::new(); 246 | let privkey = Generator::random_privkey(); 247 | let pubkey = privkey.pubkey().expect("pubkey"); 248 | let pubkey_hash = eth160(pubkey); 249 | 250 | let script = build_anyone_can_pay_script(pubkey_hash.to_owned()); 251 | let tx = gen_tx(&mut data_loader, pubkey_hash.to_owned()); 252 | let output = tx.outputs().get(0).unwrap(); 253 | let tx = tx 254 | .as_advanced_builder() 255 | .set_witnesses(Vec::new()) 256 | .set_outputs(vec![output 257 | .as_builder() 258 | .lock({ 259 | let mut args = pubkey_hash.to_vec(); 260 | // a valid args 261 | args.push(0); 262 | script.as_builder().args(Bytes::from(args).pack()).build() 263 | }) 264 | .capacity(44u64.pack()) 265 | .build()]) 266 | .build(); 267 | 268 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 269 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 270 | let verify_result = verifier.verify(MAX_CYCLES); 271 | assert_error_eq!( 272 | verify_result.unwrap_err(), 273 | ScriptError::ValidationFailure(ERROR_NO_PAIR), 274 | ); 275 | } 276 | 277 | #[test] 278 | fn test_split_cell() { 279 | let mut data_loader = DummyDataLoader::new(); 280 | let privkey = Generator::random_privkey(); 281 | let pubkey = privkey.pubkey().expect("pubkey"); 282 | let pubkey_hash = eth160(pubkey); 283 | 284 | let script = build_anyone_can_pay_script(pubkey_hash.to_owned()); 285 | let tx = gen_tx(&mut data_loader, pubkey_hash.to_owned()); 286 | let output = tx.outputs().get(0).unwrap(); 287 | let tx = tx 288 | .as_advanced_builder() 289 | .set_witnesses(Vec::new()) 290 | .set_outputs(vec![ 291 | output 292 | .clone() 293 | .as_builder() 294 | .lock(script.clone()) 295 | .capacity(44u64.pack()) 296 | .build(), 297 | output 298 | .as_builder() 299 | .lock(script) 300 | .capacity(44u64.pack()) 301 | .build(), 302 | ]) 303 | .set_outputs_data(vec![ 304 | Bytes::from(Vec::new()).pack(), 305 | Bytes::from(Vec::new()).pack(), 306 | ]) 307 | .build(); 308 | 309 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 310 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 311 | let verify_result = verifier.verify(MAX_CYCLES); 312 | assert_error_eq!( 313 | verify_result.unwrap_err(), 314 | ScriptError::ValidationFailure(ERROR_DUPLICATED_OUTPUTS), 315 | ); 316 | } 317 | 318 | #[test] 319 | fn test_merge_cell() { 320 | let mut data_loader = DummyDataLoader::new(); 321 | let privkey = Generator::random_privkey(); 322 | let pubkey = privkey.pubkey().expect("pubkey"); 323 | let pubkey_hash = eth160(pubkey); 324 | 325 | let script = build_anyone_can_pay_script(pubkey_hash.to_owned()); 326 | let mut rng = thread_rng(); 327 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 328 | let output = tx.outputs().get(0).unwrap(); 329 | let tx = tx 330 | .as_advanced_builder() 331 | .set_witnesses(Vec::new()) 332 | .set_outputs(vec![output 333 | .clone() 334 | .as_builder() 335 | .lock(script.clone()) 336 | .capacity(88u64.pack()) 337 | .build()]) 338 | .build(); 339 | 340 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 341 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 342 | let verify_result = verifier.verify(MAX_CYCLES); 343 | assert_error_eq!( 344 | verify_result.unwrap_err(), 345 | ScriptError::ValidationFailure(ERROR_DUPLICATED_INPUTS), 346 | ); 347 | } 348 | 349 | #[test] 350 | fn test_insufficient_pay() { 351 | let mut data_loader = DummyDataLoader::new(); 352 | let privkey = Generator::random_privkey(); 353 | let pubkey = privkey.pubkey().expect("pubkey"); 354 | let pubkey_hash = eth160(pubkey); 355 | 356 | let script = build_anyone_can_pay_script(pubkey_hash.to_owned()); 357 | let tx = gen_tx(&mut data_loader, pubkey_hash); 358 | let output = tx.outputs().get(0).unwrap(); 359 | let tx = tx 360 | .as_advanced_builder() 361 | .set_witnesses(Vec::new()) 362 | .set_outputs(vec![output 363 | .clone() 364 | .as_builder() 365 | .lock(script.clone()) 366 | .capacity(41u64.pack()) 367 | .build()]) 368 | .build(); 369 | 370 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 371 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 372 | let verify_result = verifier.verify(MAX_CYCLES); 373 | assert_error_eq!( 374 | verify_result.unwrap_err(), 375 | ScriptError::ValidationFailure(ERROR_OUTPUT_AMOUNT_NOT_ENOUGH), 376 | ); 377 | } 378 | 379 | #[test] 380 | fn test_payment_not_meet_requirement() { 381 | let mut data_loader = DummyDataLoader::new(); 382 | let privkey = Generator::random_privkey(); 383 | let pubkey = privkey.pubkey().expect("pubkey"); 384 | let pubkey_hash = eth160(pubkey); 385 | let mut args = pubkey_hash.to_vec(); 386 | args.push(1); 387 | let args = Bytes::from(args); 388 | let script = build_anyone_can_pay_script(args.clone()); 389 | let tx = gen_tx(&mut data_loader, args); 390 | let output = tx.outputs().get(0).unwrap(); 391 | let tx = tx 392 | .as_advanced_builder() 393 | .set_witnesses(Vec::new()) 394 | .set_outputs(vec![output 395 | .clone() 396 | .as_builder() 397 | .lock(script.clone()) 398 | .capacity(44u64.pack()) 399 | .build()]) 400 | .build(); 401 | 402 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 403 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 404 | let verify_result = verifier.verify(MAX_CYCLES); 405 | assert_error_eq!( 406 | verify_result.unwrap_err(), 407 | ScriptError::ValidationFailure(ERROR_OUTPUT_AMOUNT_NOT_ENOUGH), 408 | ); 409 | } 410 | 411 | #[test] 412 | fn test_no_pair() { 413 | let mut data_loader = DummyDataLoader::new(); 414 | let privkey = Generator::random_privkey(); 415 | let pubkey = privkey.pubkey().expect("pubkey"); 416 | let pubkey_hash = eth160(pubkey); 417 | 418 | let another_script = build_anyone_can_pay_script(vec![42].into()); 419 | let tx = gen_tx(&mut data_loader, pubkey_hash.to_owned()); 420 | let output = tx.outputs().get(0).unwrap(); 421 | let tx = tx 422 | .as_advanced_builder() 423 | .set_witnesses(Vec::new()) 424 | .set_outputs(vec![output 425 | .clone() 426 | .as_builder() 427 | .lock(another_script.clone()) 428 | .capacity(44u64.pack()) 429 | .build()]) 430 | .build(); 431 | 432 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 433 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 434 | let verify_result = verifier.verify(MAX_CYCLES); 435 | assert_error_eq!( 436 | verify_result.unwrap_err(), 437 | ScriptError::ValidationFailure(ERROR_NO_PAIR), 438 | ); 439 | } 440 | 441 | #[test] 442 | fn test_overflow() { 443 | let mut data_loader = DummyDataLoader::new(); 444 | let privkey = Generator::random_privkey(); 445 | let pubkey = privkey.pubkey().expect("pubkey"); 446 | let pubkey_hash = eth160(pubkey); 447 | 448 | let mut args = pubkey_hash.to_vec(); 449 | args.push(255); 450 | let args = Bytes::from(args); 451 | 452 | let script = build_anyone_can_pay_script(args.to_owned()); 453 | let tx = gen_tx(&mut data_loader, args); 454 | let output = tx.outputs().get(0).unwrap(); 455 | let tx = tx 456 | .as_advanced_builder() 457 | .set_witnesses(Vec::new()) 458 | .set_outputs(vec![output 459 | .as_builder() 460 | .lock(script) 461 | .capacity(44u64.pack()) 462 | .build()]) 463 | .build(); 464 | 465 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 466 | let verifier = TransactionScriptsVerifier::new(&resolved_tx, &data_loader); 467 | let verify_result = verifier.verify(MAX_CYCLES); 468 | 469 | assert_error_eq!( 470 | verify_result.unwrap_err(), 471 | ScriptError::ValidationFailure(ERROR_OVERFLOW), 472 | ); 473 | } 474 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod anyone_can_pay; 2 | mod secp256k1_keccak256_sighash_all; 3 | mod secp256k1_keccak256_sighash_all_acpl_compatibility; 4 | 5 | use bech32::{self, ToBase32}; 6 | use ckb_crypto::secp::{Privkey, Pubkey}; 7 | use ckb_fixed_hash::H512; 8 | use ckb_script::DataLoader; 9 | use ckb_types::{ 10 | bytes::Bytes, 11 | core::{cell::CellMeta, BlockExt, EpochExt, HeaderView, TransactionView}, 12 | packed::{ 13 | self, Byte32, CellInputVec, CellOutput, CellOutputVec, OutPoint, Script, WitnessArgs, 14 | }, 15 | prelude::*, 16 | H256, 17 | }; 18 | use secp256k1::key; 19 | 20 | use lazy_static::lazy_static; 21 | use std::collections::HashMap; 22 | 23 | use sha3::{Digest, Keccak256}; 24 | 25 | pub const MAX_CYCLES: u64 = std::u64::MAX; 26 | pub const SIGNATURE_SIZE: usize = 65; 27 | 28 | lazy_static! { 29 | pub static ref SECP256K1_DATA_BIN: Bytes = 30 | Bytes::from(&include_bytes!("../../specs/cells/secp256k1_data")[..]); 31 | pub static ref KECCAK256_ALL_BIN: Bytes = 32 | Bytes::from(&include_bytes!("../../specs/cells/secp256k1_keccak256_sighash_all")[..]); 33 | pub static ref KECCAK256_ALL_ACPL_BIN: Bytes = 34 | Bytes::from(&include_bytes!("../../specs/cells/secp256k1_keccak256_sighash_all_acpl")[..]); 35 | pub static ref CKB_CELL_UPGRADE_BIN: Bytes = 36 | Bytes::from(&include_bytes!("../../specs/cells/ckb_cell_upgrade")[..]); 37 | } 38 | 39 | #[derive(Default)] 40 | pub struct DummyDataLoader { 41 | pub cells: HashMap, 42 | pub headers: HashMap, 43 | pub epoches: HashMap, 44 | } 45 | 46 | impl DummyDataLoader { 47 | fn new() -> Self { 48 | Self::default() 49 | } 50 | } 51 | 52 | impl DataLoader for DummyDataLoader { 53 | // load Cell Data 54 | fn load_cell_data(&self, cell: &CellMeta) -> Option<(Bytes, Byte32)> { 55 | cell.mem_cell_data.clone().or_else(|| { 56 | self.cells 57 | .get(&cell.out_point) 58 | .map(|(_, data)| (data.clone(), CellOutput::calc_data_hash(&data))) 59 | }) 60 | } 61 | // load BlockExt 62 | fn get_block_ext(&self, _hash: &Byte32) -> Option { 63 | unreachable!() 64 | } 65 | 66 | // load header 67 | fn get_header(&self, block_hash: &Byte32) -> Option { 68 | self.headers.get(block_hash).cloned() 69 | } 70 | 71 | // load EpochExt 72 | fn get_block_epoch(&self, block_hash: &Byte32) -> Option { 73 | self.epoches.get(block_hash).cloned() 74 | } 75 | } 76 | 77 | // pub fn eth160(message: &[u8]) -> Bytes { 78 | pub fn eth160(pubkey1: Pubkey) -> Bytes { 79 | let prefix_key: [u8; 65] = { 80 | let mut temp = [4u8; 65]; 81 | let h512: H512 = pubkey1.into(); 82 | temp[1..65].copy_from_slice(h512.as_bytes()); 83 | temp 84 | }; 85 | let pubkey = key::PublicKey::from_slice(&prefix_key).unwrap(); 86 | let message = Vec::from(&pubkey.serialize_uncompressed()[1..]); 87 | // let message = Vec::from(&pubkey.serialize()[..]); 88 | 89 | // println!("{}", faster_hex::hex_string(&message).unwrap()); 90 | // println!("{}", faster_hex::hex_string(&message1).unwrap()); 91 | 92 | let mut hasher = Keccak256::default(); 93 | hasher.input(&message); 94 | Bytes::from(hasher.result().as_slice()).slice(12, 32) 95 | } 96 | 97 | pub fn sign_tx_keccak256( 98 | dummy: &mut DummyDataLoader, 99 | tx: TransactionView, 100 | key: &Privkey, 101 | ) -> TransactionView { 102 | let witnesses_len = tx.witnesses().len(); 103 | sign_tx_by_input_group_keccak256(dummy, tx, key, 0, witnesses_len) 104 | } 105 | 106 | pub fn sign_tx_by_input_group_keccak256( 107 | dummy: &mut DummyDataLoader, 108 | tx: TransactionView, 109 | key: &Privkey, 110 | begin_index: usize, 111 | len: usize, 112 | ) -> TransactionView { 113 | let tx_hash = tx.hash(); 114 | let mut signed_witnesses: Vec = tx 115 | .inputs() 116 | .into_iter() 117 | .enumerate() 118 | .map(|(i, _)| { 119 | if i == begin_index { 120 | // let mut blake2b = ckb_hash::new_blake2b(); 121 | let mut hasher = Keccak256::default(); 122 | let mut message = [0u8; 32]; 123 | 124 | // blake2b.update(&tx_hash.raw_data()); 125 | hasher.input(&tx_hash.raw_data()); 126 | // digest the first witness 127 | let witness = WitnessArgs::new_unchecked(tx.witnesses().get(i).unwrap().unpack()); 128 | let zero_lock: Bytes = { 129 | let mut buf = Vec::new(); 130 | buf.resize(SIGNATURE_SIZE, 0); 131 | buf.into() 132 | }; 133 | let witness_for_digest = 134 | witness.clone().as_builder().lock(zero_lock.pack()).build(); 135 | let witness_len = witness_for_digest.as_bytes().len() as u64; 136 | // blake2b.update(&witness_len.to_le_bytes()); 137 | // blake2b.update(&witness_for_digest.as_bytes()); 138 | hasher.input(&witness_len.to_le_bytes()); 139 | hasher.input(&witness_for_digest.as_bytes()); 140 | ((i + 1)..(i + len)).for_each(|n| { 141 | let witness = tx.witnesses().get(n).unwrap(); 142 | let witness_len = witness.raw_data().len() as u64; 143 | // blake2b.update(&witness_len.to_le_bytes()); 144 | // blake2b.update(&witness.raw_data()); 145 | hasher.input(&witness_len.to_le_bytes()); 146 | hasher.input(&witness.raw_data()); 147 | }); 148 | // blake2b.finalize(&mut message); 149 | message.copy_from_slice(&hasher.result()[0..32]); 150 | 151 | let prefix: [u8; 28] = [ 152 | 0x19, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x20, 0x53, 0x69, 0x67, 153 | 0x6e, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x0a, 154 | 0x33, 0x32, 155 | ]; 156 | hasher = Keccak256::default(); 157 | hasher.input(&prefix); 158 | hasher.input(&message); 159 | message.copy_from_slice(&hasher.result()[0..32]); 160 | 161 | // let message = H256::from(message); 162 | let message = get_tx_typed_data_hash(dummy, message, tx.inputs(), tx.outputs()); 163 | let sig = key.sign_recoverable(&message).expect("sign"); 164 | witness 165 | .as_builder() 166 | .lock(sig.serialize().pack()) 167 | .build() 168 | .as_bytes() 169 | .pack() 170 | } else { 171 | tx.witnesses().get(i).unwrap_or_default() 172 | } 173 | }) 174 | .collect(); 175 | for i in signed_witnesses.len()..tx.witnesses().len() { 176 | signed_witnesses.push(tx.witnesses().get(i).unwrap()); 177 | } 178 | // calculate message 179 | tx.as_advanced_builder() 180 | .set_witnesses(signed_witnesses) 181 | .build() 182 | } 183 | 184 | pub fn get_tx_typed_data_hash( 185 | dummy: &mut DummyDataLoader, 186 | tx_hash: [u8; 32], 187 | inputs: CellInputVec, 188 | outputs: CellOutputVec, 189 | ) -> H256 { 190 | let mut message = [0u8; 32]; 191 | let typeddata_prefix: [u8; 2] = [0x19, 0x01]; 192 | let ckbtransaction_typehash: [u8; 32] = [ 193 | 0x17, 0xe4, 0x04, 0xd0, 0xcd, 0xcc, 0x43, 0x1e, 0xe6, 0xdf, 0x80, 0x7a, 0xbc, 0xcc, 0x69, 194 | 0x5d, 0x95, 0xd0, 0x38, 0xf5, 0x76, 0x47, 0xe2, 0xef, 0x92, 0xb9, 0x68, 0x66, 0xca, 0xe5, 195 | 0x9d, 0x04, 196 | ]; 197 | let output_typehash: [u8; 32] = [ 198 | 0xef, 0xdd, 0x9a, 0xc6, 0xc9, 0x8f, 0xcb, 0xab, 0xc5, 0x2e, 0xf1, 0xd8, 0xa4, 0xd3, 0xac, 199 | 0xcd, 0x43, 0x96, 0x36, 0x2a, 0x21, 0x1c, 0xbf, 0x7a, 0x3c, 0x20, 0xc2, 0x89, 0x22, 0x08, 200 | 0x19, 0x13, 201 | ]; 202 | let domain_separator: [u8; 32] = [ 203 | 0xec, 0x9e, 0x64, 0xcb, 0x49, 0x31, 0x37, 0x85, 0x0e, 0x3d, 0x5d, 0x47, 0x3c, 0xa1, 0x09, 204 | 0xea, 0xe1, 0x47, 0xad, 0xb8, 0xa6, 0xbf, 0x46, 0x0b, 0xf2, 0x06, 0xe9, 0x0f, 0x62, 0x64, 205 | 0x2e, 0x3f, 206 | ]; 207 | 208 | let mut hasher = Keccak256::default(); 209 | 210 | let mut input_capacities: u64 = 0; 211 | let len = inputs.len(); 212 | (0..len).for_each(|n| { 213 | let input_cell = inputs.get(n).unwrap(); 214 | let previous_outpoint = input_cell.previous_output(); 215 | let val = dummy.cells.get(&previous_outpoint); 216 | let (cell, _) = val.unwrap(); 217 | 218 | let capacity = cell.capacity(); 219 | let mut bytes = [0u8; 8]; 220 | bytes.copy_from_slice(capacity.as_slice()); 221 | let c64 = u64::from_le_bytes(bytes); 222 | input_capacities += c64; 223 | }); 224 | 225 | println!("input capacities {}", input_capacities); 226 | 227 | let mut output_capacities: u64 = 0; 228 | 229 | let len = outputs.len(); 230 | (0..len).for_each(|n| { 231 | let output_cell = outputs.get(n).unwrap(); 232 | let capacity = output_cell.capacity(); 233 | 234 | let mut bytes = [0u8; 8]; 235 | bytes.copy_from_slice(capacity.as_slice()); 236 | let c64 = u64::from_le_bytes(bytes); 237 | output_capacities += c64; 238 | 239 | let mut output_hasher = Keccak256::default(); 240 | output_hasher.input(&output_typehash); 241 | 242 | output_hasher.input(&hash_address(output_cell.lock())); 243 | output_hasher.input(&hash_amount(c64)); 244 | 245 | hasher.input(&output_hasher.result()); 246 | }); 247 | 248 | let output_hash = hasher.result(); 249 | 250 | hasher = Keccak256::default(); 251 | hasher.input(&ckbtransaction_typehash); 252 | hasher.input(&tx_hash); 253 | hasher.input(&hash_amount(input_capacities - output_capacities)); 254 | hasher.input(&hash_amount(input_capacities)); 255 | hasher.input(&output_hash); 256 | 257 | let ckb_type_hash = hasher.result(); 258 | 259 | hasher = Keccak256::default(); 260 | hasher.input(&typeddata_prefix); 261 | hasher.input(&domain_separator); 262 | hasher.input(&ckb_type_hash); 263 | 264 | message.copy_from_slice(&hasher.result()[0..32]); 265 | 266 | H256::from(message) 267 | } 268 | 269 | pub fn hash_amount(amount: u64) -> [u8; 32] { 270 | let mut message = [0u8; 32]; 271 | let formated_capacity = format!("{:.8}CKB", (amount as f64) / 100000000.0); 272 | 273 | println!( 274 | "formated_capacity: {}, {}, {}", 275 | amount, 276 | formated_capacity, 277 | formated_capacity.len() 278 | ); 279 | let mut capacity_hasher = Keccak256::default(); 280 | capacity_hasher.input(&formated_capacity.as_bytes()); 281 | message.copy_from_slice(&capacity_hasher.result()[0..32]); 282 | message 283 | } 284 | 285 | pub fn hash_address(lock: Script) -> [u8; 32] { 286 | let mut message = [0u8; 32]; 287 | 288 | let args = lock.args(); 289 | let code_hash = lock.code_hash(); 290 | let hash_type = lock.hash_type(); 291 | let ckb_address: String; 292 | if code_hash.raw_data().to_vec().eq(&[0u8; 32]) { 293 | ckb_address = String::from("unknown"); 294 | } else { 295 | let secp256k1_blake160_sighash_all_type_hash: [u8; 32] = [ 296 | 0x9b, 0xd7, 0xe0, 0x6f, 0x3e, 0xcf, 0x4b, 0xe0, 0xf2, 0xfc, 0xd2, 0x18, 0x8b, 0x23, 297 | 0xf1, 0xb9, 0xfc, 0xc8, 0x8e, 0x5d, 0x4b, 0x65, 0xa8, 0x63, 0x7b, 0x17, 0x72, 0x3b, 298 | 0xbd, 0xa3, 0xcc, 0xe8, 299 | ]; 300 | let secp256k1_blake160_multisig_all_type_hash: [u8; 32] = [ 301 | 0x5c, 0x50, 0x69, 0xeb, 0x08, 0x57, 0xef, 0xc6, 0x5e, 0x1b, 0xca, 0x0c, 0x07, 0xdf, 302 | 0x34, 0xc3, 0x16, 0x63, 0xb3, 0x62, 0x2f, 0xd3, 0x87, 0x6c, 0x87, 0x63, 0x20, 0xfc, 303 | 0x96, 0x34, 0xe2, 0xa8, 304 | ]; 305 | 306 | let mut prefix; 307 | if code_hash.raw_data().to_vec().eq(&secp256k1_blake160_sighash_all_type_hash) { 308 | prefix = vec![0x01, 0x00]; 309 | } else if code_hash.raw_data().to_vec().eq(&secp256k1_blake160_multisig_all_type_hash) 310 | { 311 | prefix = vec![0x01, 0x01]; 312 | } else { 313 | if hash_type.as_bytes()[0] == 0x01 { 314 | prefix = vec![0x04]; 315 | } else { 316 | prefix = vec![0x02]; 317 | } 318 | prefix.extend(&code_hash.raw_data()); 319 | } 320 | prefix.extend(&args.raw_data()); 321 | let address = bech32::encode("ckt", prefix.to_base32()).unwrap(); 322 | 323 | if address.len() <= 17 { 324 | ckb_address = address; 325 | } else { 326 | ckb_address = format!("{}...{}", &address[0..7], &address[(address.len() - 7)..]); 327 | } 328 | } 329 | 330 | let mut address_hasher = Keccak256::default(); 331 | address_hasher.input(&ckb_address.as_bytes()); 332 | message.copy_from_slice(&address_hasher.result()[0..32]); 333 | message 334 | } 335 | -------------------------------------------------------------------------------- /src/tests/secp256k1_keccak256_sighash_all.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | eth160, sign_tx_by_input_group_keccak256, sign_tx_keccak256, DummyDataLoader, 3 | KECCAK256_ALL_BIN, MAX_CYCLES, SECP256K1_DATA_BIN, 4 | }; 5 | use ckb_crypto::secp::{Generator, Privkey}; 6 | use ckb_error::assert_error_eq; 7 | use ckb_script::{ScriptError, TransactionScriptsVerifier}; 8 | use ckb_types::{ 9 | bytes::Bytes, 10 | core::{ 11 | cell::{CellMetaBuilder, ResolvedTransaction}, 12 | Capacity, DepType, ScriptHashType, TransactionBuilder, TransactionView, 13 | }, 14 | packed::{CellDep, CellInput, CellOutput, OutPoint, Script, WitnessArgs, WitnessArgsBuilder}, 15 | prelude::*, 16 | H256, 17 | }; 18 | use rand::{thread_rng, Rng, SeedableRng}; 19 | 20 | use sha3::{Digest, Keccak256}; 21 | 22 | const ERROR_ENCODING: i8 = -2; 23 | const ERROR_WITNESS_SIZE: i8 = -22; 24 | const ERROR_PUBKEY_BLAKE160_HASH: i8 = -31; 25 | 26 | fn gen_tx(dummy: &mut DummyDataLoader, lock_args: Bytes) -> TransactionView { 27 | let mut rng = thread_rng(); 28 | gen_tx_with_grouped_args(dummy, vec![(lock_args, 1)], &mut rng) 29 | } 30 | 31 | fn gen_tx_with_grouped_args( 32 | dummy: &mut DummyDataLoader, 33 | grouped_args: Vec<(Bytes, usize)>, 34 | rng: &mut R, 35 | ) -> TransactionView { 36 | // setup sighash_all dep 37 | let sighash_all_out_point = { 38 | let contract_tx_hash = { 39 | let mut buf = [0u8; 32]; 40 | rng.fill(&mut buf); 41 | buf.pack() 42 | }; 43 | OutPoint::new(contract_tx_hash.clone(), 0) 44 | }; 45 | // dep contract code 46 | let sighash_all_cell = CellOutput::new_builder() 47 | .capacity( 48 | Capacity::bytes(KECCAK256_ALL_BIN.len()) 49 | .expect("script capacity") 50 | .pack(), 51 | ) 52 | .build(); 53 | let sighash_all_cell_data_hash = CellOutput::calc_data_hash(&KECCAK256_ALL_BIN); 54 | dummy.cells.insert( 55 | sighash_all_out_point.clone(), 56 | (sighash_all_cell, KECCAK256_ALL_BIN.clone()), 57 | ); 58 | // setup secp256k1_data dep 59 | let secp256k1_data_out_point = { 60 | let tx_hash = { 61 | let mut buf = [0u8; 32]; 62 | rng.fill(&mut buf); 63 | buf.pack() 64 | }; 65 | OutPoint::new(tx_hash, 0) 66 | }; 67 | let secp256k1_data_cell = CellOutput::new_builder() 68 | .capacity( 69 | Capacity::bytes(SECP256K1_DATA_BIN.len()) 70 | .expect("data capacity") 71 | .pack(), 72 | ) 73 | .build(); 74 | dummy.cells.insert( 75 | secp256k1_data_out_point.clone(), 76 | (secp256k1_data_cell, SECP256K1_DATA_BIN.clone()), 77 | ); 78 | // setup default tx builder 79 | 80 | let block_assembler_code_hash: [u8; 32] = [ 81 | 0x9b, 0xd7, 0xe0, 0x6f, 0x3e, 0xcf, 0x4b, 0xe0, 0xf2, 0xfc, 0xd2, 0x18, 0x8b, 0x23, 0xf1, 82 | 0xb9, 0xfc, 0xc8, 0x8e, 0x5d, 0x4b, 0x65, 0xa8, 0x63, 0x7b, 0x17, 0x72, 0x3b, 0xbd, 0xa3, 83 | 0xcc, 0xe8, 84 | ]; 85 | let lock_script = Script::new_builder() 86 | .code_hash(block_assembler_code_hash.pack()) 87 | .args([0u8; 33].pack()) 88 | .hash_type(ScriptHashType::Type.into()) 89 | .build(); 90 | let dummy_capacity = Capacity::shannons(42); 91 | let mut tx_builder = TransactionBuilder::default() 92 | .cell_dep( 93 | CellDep::new_builder() 94 | .out_point(sighash_all_out_point) 95 | .dep_type(DepType::Code.into()) 96 | .build(), 97 | ) 98 | .cell_dep( 99 | CellDep::new_builder() 100 | .out_point(secp256k1_data_out_point) 101 | .dep_type(DepType::Code.into()) 102 | .build(), 103 | ) 104 | .output( 105 | CellOutput::new_builder() 106 | .capacity(dummy_capacity.pack()) 107 | .lock(lock_script) 108 | .build(), 109 | ) 110 | .output_data(Bytes::new().pack()); 111 | 112 | for (args, inputs_size) in grouped_args { 113 | // setup dummy input unlock script 114 | for _ in 0..inputs_size { 115 | let previous_tx_hash = { 116 | let mut buf = [0u8; 32]; 117 | rng.fill(&mut buf); 118 | buf.pack() 119 | }; 120 | let previous_out_point = OutPoint::new(previous_tx_hash, 0); 121 | let script = Script::new_builder() 122 | .args(args.pack()) 123 | .code_hash(sighash_all_cell_data_hash.clone()) 124 | .hash_type(ScriptHashType::Data.into()) 125 | .build(); 126 | let previous_output_cell = CellOutput::new_builder() 127 | .capacity(dummy_capacity.pack()) 128 | .lock(script) 129 | .build(); 130 | dummy.cells.insert( 131 | previous_out_point.clone(), 132 | (previous_output_cell.clone(), Bytes::new()), 133 | ); 134 | let mut random_extra_witness = [0u8; 32]; 135 | rng.fill(&mut random_extra_witness); 136 | let witness_args = WitnessArgsBuilder::default() 137 | .extra(Bytes::from(random_extra_witness.to_vec()).pack()) 138 | .build(); 139 | tx_builder = tx_builder 140 | .input(CellInput::new(previous_out_point, 0)) 141 | .witness(witness_args.as_bytes().pack()); 142 | } 143 | } 144 | 145 | tx_builder.build() 146 | } 147 | 148 | fn sign_tx_hash(tx: TransactionView, key: &Privkey, tx_hash: &[u8]) -> TransactionView { 149 | // calculate message 150 | // let mut blake2b = ckb_hash::new_blake2b(); 151 | let mut hasher = Keccak256::default(); 152 | let mut message = [0u8; 32]; 153 | // blake2b.update(tx_hash); 154 | // blake2b.finalize(&mut message); 155 | hasher.input(tx_hash); 156 | message.copy_from_slice(&hasher.result()[0..32]); 157 | let message = H256::from(message); 158 | let sig = key.sign_recoverable(&message).expect("sign"); 159 | let witness_args = WitnessArgsBuilder::default() 160 | .lock(Bytes::from(sig.serialize()).pack()) 161 | .build(); 162 | tx.as_advanced_builder() 163 | .set_witnesses(vec![witness_args.as_bytes().pack()]) 164 | .build() 165 | } 166 | 167 | fn build_resolved_tx(data_loader: &DummyDataLoader, tx: &TransactionView) -> ResolvedTransaction { 168 | let resolved_cell_deps = tx 169 | .cell_deps() 170 | .into_iter() 171 | .map(|dep| { 172 | let deps_out_point = dep.clone(); 173 | let (dep_output, dep_data) = 174 | data_loader.cells.get(&deps_out_point.out_point()).unwrap(); 175 | CellMetaBuilder::from_cell_output(dep_output.to_owned(), dep_data.to_owned()) 176 | .out_point(deps_out_point.out_point().clone()) 177 | .build() 178 | }) 179 | .collect(); 180 | 181 | let mut resolved_inputs = Vec::new(); 182 | for i in 0..tx.inputs().len() { 183 | let previous_out_point = tx.inputs().get(i).unwrap().previous_output(); 184 | let (input_output, input_data) = data_loader.cells.get(&previous_out_point).unwrap(); 185 | resolved_inputs.push( 186 | CellMetaBuilder::from_cell_output(input_output.to_owned(), input_data.to_owned()) 187 | .out_point(previous_out_point) 188 | .build(), 189 | ); 190 | } 191 | 192 | ResolvedTransaction { 193 | transaction: tx.clone(), 194 | resolved_cell_deps, 195 | resolved_inputs, 196 | resolved_dep_groups: vec![], 197 | } 198 | } 199 | 200 | #[test] 201 | fn test_keccak_all_unlock() { 202 | let mut data_loader = DummyDataLoader::new(); 203 | let privkey = Generator::random_privkey(); 204 | let pubkey = privkey.pubkey().expect("pubkey"); 205 | let pubkey_hash = eth160(pubkey); 206 | let tx = gen_tx(&mut data_loader, pubkey_hash); 207 | let tx = sign_tx_keccak256(&mut data_loader, tx, &privkey); 208 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 209 | let verify_result = 210 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 211 | verify_result.expect("pass verification"); 212 | } 213 | 214 | #[test] 215 | fn test_sighash_all_with_extra_witness_unlock() { 216 | let mut data_loader = DummyDataLoader::new(); 217 | let privkey = Generator::random_privkey(); 218 | let pubkey = privkey.pubkey().expect("pubkey"); 219 | let pubkey_hash = eth160(pubkey); 220 | let tx = gen_tx(&mut data_loader, pubkey_hash); 221 | let extract_witness = vec![1, 2, 3, 4]; 222 | let tx = tx 223 | .as_advanced_builder() 224 | .set_witnesses(vec![WitnessArgs::new_builder() 225 | .extra(Bytes::from(extract_witness).pack()) 226 | .build() 227 | .as_bytes() 228 | .pack()]) 229 | .build(); 230 | { 231 | let tx = sign_tx_keccak256(&mut data_loader, tx.clone(), &privkey); 232 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 233 | let verify_result = 234 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 235 | verify_result.expect("pass verification"); 236 | } 237 | { 238 | let tx = sign_tx_keccak256(&mut data_loader, tx, &privkey); 239 | let wrong_witness = tx 240 | .witnesses() 241 | .get(0) 242 | .map(|w| { 243 | WitnessArgs::new_unchecked(w.unpack()) 244 | .as_builder() 245 | .extra(Bytes::from(vec![0]).pack()) 246 | .build() 247 | }) 248 | .unwrap(); 249 | let tx = tx 250 | .as_advanced_builder() 251 | .set_witnesses(vec![wrong_witness.as_bytes().pack()]) 252 | .build(); 253 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 254 | let verify_result = 255 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 256 | assert_error_eq!( 257 | verify_result.unwrap_err(), 258 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 259 | ); 260 | } 261 | } 262 | 263 | #[test] 264 | fn test_sighash_all_with_grouped_inputs_unlock() { 265 | let mut rng = thread_rng(); 266 | let mut data_loader = DummyDataLoader::new(); 267 | let privkey = Generator::random_privkey(); 268 | let pubkey = privkey.pubkey().expect("pubkey"); 269 | let pubkey_hash = eth160(pubkey); 270 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 271 | { 272 | let tx = sign_tx_keccak256(&mut data_loader, tx.clone(), &privkey); 273 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 274 | let verify_result = 275 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 276 | verify_result.expect("pass verification"); 277 | } 278 | { 279 | let tx = sign_tx_keccak256(&mut data_loader, tx.clone(), &privkey); 280 | let wrong_witness = tx 281 | .witnesses() 282 | .get(1) 283 | .map(|w| { 284 | WitnessArgs::new_unchecked(w.unpack()) 285 | .as_builder() 286 | .extra(Bytes::from(vec![0]).pack()) 287 | .build() 288 | }) 289 | .unwrap(); 290 | let tx = tx 291 | .as_advanced_builder() 292 | .set_witnesses(vec![ 293 | tx.witnesses().get(0).unwrap(), 294 | wrong_witness.as_bytes().pack(), 295 | ]) 296 | .build(); 297 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 298 | let verify_result = 299 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 300 | assert_error_eq!( 301 | verify_result.unwrap_err(), 302 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 303 | ); 304 | } 305 | } 306 | 307 | #[test] 308 | fn test_sighash_all_with_2_different_inputs_unlock() { 309 | let mut rng = thread_rng(); 310 | let mut data_loader = DummyDataLoader::new(); 311 | // key1 312 | let privkey = Generator::random_privkey(); 313 | let pubkey = privkey.pubkey().expect("pubkey"); 314 | let pubkey_hash = eth160(pubkey); 315 | // key2 316 | let privkey2 = Generator::random_privkey(); 317 | let pubkey2 = privkey2.pubkey().expect("pubkey"); 318 | let pubkey_hash2 = eth160(pubkey2); 319 | 320 | // sign with 2 keys 321 | let tx = gen_tx_with_grouped_args( 322 | &mut data_loader, 323 | vec![(pubkey_hash, 2), (pubkey_hash2, 2)], 324 | &mut rng, 325 | ); 326 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 2); 327 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey2, 2, 2); 328 | 329 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 330 | let verify_result = 331 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 332 | verify_result.expect("pass verification"); 333 | } 334 | 335 | #[test] 336 | fn test_signing_with_wrong_key() { 337 | let mut data_loader = DummyDataLoader::new(); 338 | let privkey = Generator::random_privkey(); 339 | let wrong_privkey = Generator::random_privkey(); 340 | let pubkey = privkey.pubkey().expect("pubkey"); 341 | let pubkey_hash = eth160(pubkey); 342 | let tx = gen_tx(&mut data_loader, pubkey_hash); 343 | let tx = sign_tx_keccak256(&mut data_loader, tx, &wrong_privkey); 344 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 345 | let verify_result = 346 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 347 | assert_error_eq!( 348 | verify_result.unwrap_err(), 349 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 350 | ); 351 | } 352 | 353 | #[test] 354 | fn test_signing_wrong_tx_hash() { 355 | let mut data_loader = DummyDataLoader::new(); 356 | let privkey = Generator::random_privkey(); 357 | let pubkey = privkey.pubkey().expect("pubkey"); 358 | let pubkey_hash = eth160(pubkey); 359 | let tx = gen_tx(&mut data_loader, pubkey_hash); 360 | let tx = { 361 | let mut rand_tx_hash = [0u8; 32]; 362 | let mut rng = thread_rng(); 363 | rng.fill(&mut rand_tx_hash); 364 | sign_tx_hash(tx, &privkey, &rand_tx_hash[..]) 365 | }; 366 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 367 | let verify_result = 368 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 369 | assert_error_eq!( 370 | verify_result.unwrap_err(), 371 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 372 | ); 373 | } 374 | 375 | #[test] 376 | fn test_super_long_witness() { 377 | let mut data_loader = DummyDataLoader::new(); 378 | let privkey = Generator::random_privkey(); 379 | let pubkey = privkey.pubkey().expect("pubkey"); 380 | let pubkey_hash = eth160(pubkey); 381 | let tx = gen_tx(&mut data_loader, pubkey_hash); 382 | let tx_hash = tx.hash(); 383 | 384 | let mut buffer: Vec = vec![]; 385 | buffer.resize(40000, 1); 386 | let super_long_message = Bytes::from(&buffer[..]); 387 | 388 | // let mut blake2b = ckb_hash::new_blake2b(); 389 | let mut hasher = Keccak256::default(); 390 | let mut message = [0u8; 32]; 391 | // blake2b.update(&tx_hash.raw_data()); 392 | // blake2b.update(&super_long_message[..]); 393 | // blake2b.finalize(&mut message); 394 | hasher.input(&tx_hash.raw_data()); 395 | hasher.input(&super_long_message[..]); 396 | message.copy_from_slice(&hasher.result()[0..32]); 397 | let message = H256::from(message); 398 | let sig = privkey.sign_recoverable(&message).expect("sign"); 399 | let witness = WitnessArgs::new_builder() 400 | .lock(Bytes::from(sig.serialize()).pack()) 401 | .extra(super_long_message.pack()) 402 | .build(); 403 | let tx = tx 404 | .as_advanced_builder() 405 | .set_witnesses(vec![witness.as_bytes().pack()]) 406 | .build(); 407 | 408 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 409 | let verify_result = 410 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 411 | assert_error_eq!( 412 | verify_result.unwrap_err(), 413 | ScriptError::ValidationFailure(ERROR_WITNESS_SIZE), 414 | ); 415 | } 416 | 417 | #[test] 418 | fn test_sighash_all_2_in_2_out_cycles() { 419 | const CONSUME_CYCLES: u64 = 7778845; 420 | 421 | let mut data_loader = DummyDataLoader::new(); 422 | let mut generator = Generator::non_crypto_safe_prng(42); 423 | let mut rng = rand::rngs::SmallRng::seed_from_u64(42); 424 | 425 | // key1 426 | let privkey = generator.gen_privkey(); 427 | let pubkey = privkey.pubkey().expect("pubkey"); 428 | let pubkey_hash = eth160(pubkey); 429 | // key2 430 | let privkey2 = generator.gen_privkey(); 431 | let pubkey2 = privkey2.pubkey().expect("pubkey"); 432 | let pubkey_hash2 = eth160(pubkey2); 433 | 434 | // sign with 2 keys 435 | let tx = gen_tx_with_grouped_args( 436 | &mut data_loader, 437 | vec![(pubkey_hash, 1), (pubkey_hash2, 1)], 438 | &mut rng, 439 | ); 440 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 1); 441 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey2, 1, 1); 442 | 443 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 444 | let verify_result = 445 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 446 | let cycles = verify_result.expect("pass verification"); 447 | assert_eq!(CONSUME_CYCLES, cycles) 448 | } 449 | 450 | #[test] 451 | fn test_sighash_all_witness_append_junk_data() { 452 | let mut rng = thread_rng(); 453 | let mut data_loader = DummyDataLoader::new(); 454 | let privkey = Generator::random_privkey(); 455 | let pubkey = privkey.pubkey().expect("pubkey"); 456 | let pubkey_hash = eth160(pubkey); 457 | 458 | // sign with 2 keys 459 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 460 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 2); 461 | let mut witnesses: Vec<_> = Unpack::>::unpack(&tx.witnesses()); 462 | // append junk data to first witness 463 | let mut witness = Vec::new(); 464 | witness.resize(witnesses[0].len(), 0); 465 | witness.copy_from_slice(&witnesses[0]); 466 | witness.push(0); 467 | witnesses[0] = witness.into(); 468 | 469 | let tx = tx 470 | .as_advanced_builder() 471 | .set_witnesses(witnesses.into_iter().map(|w| w.pack()).collect()) 472 | .build(); 473 | 474 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 475 | let verify_result = 476 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 477 | assert_error_eq!( 478 | verify_result.unwrap_err(), 479 | ScriptError::ValidationFailure(ERROR_ENCODING), 480 | ); 481 | } 482 | 483 | #[test] 484 | fn test_sighash_all_witness_args_ambiguity() { 485 | // This test case build tx with WitnessArgs(lock, data, "") 486 | // and try unlock with WitnessArgs(lock, "", data) 487 | // 488 | // this case will fail if contract use a naive function to digest witness. 489 | 490 | let mut rng = thread_rng(); 491 | let mut data_loader = DummyDataLoader::new(); 492 | let privkey = Generator::random_privkey(); 493 | let pubkey = privkey.pubkey().expect("pubkey"); 494 | let pubkey_hash = eth160(pubkey); 495 | 496 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 497 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 2); 498 | let witnesses: Vec<_> = Unpack::>::unpack(&tx.witnesses()); 499 | // move extra data to type_ 500 | let witnesses: Vec<_> = witnesses 501 | .into_iter() 502 | .map(|witness| { 503 | let witness = WitnessArgs::new_unchecked(witness); 504 | let data = witness.extra().clone(); 505 | witness 506 | .as_builder() 507 | .extra(Bytes::new().pack()) 508 | .type_(data) 509 | .build() 510 | }) 511 | .collect(); 512 | 513 | let tx = tx 514 | .as_advanced_builder() 515 | .set_witnesses(witnesses.into_iter().map(|w| w.as_bytes().pack()).collect()) 516 | .build(); 517 | 518 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 519 | let verify_result = 520 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 521 | assert_error_eq!( 522 | verify_result.unwrap_err(), 523 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 524 | ); 525 | } 526 | 527 | #[test] 528 | fn test_sighash_all_witnesses_ambiguity() { 529 | // This test case sign tx with [witness1, "", witness2] 530 | // and try unlock with [witness1, witness2, ""] 531 | // 532 | // this case will fail if contract use a naive function to digest witness. 533 | 534 | let mut rng = thread_rng(); 535 | let mut data_loader = DummyDataLoader::new(); 536 | let privkey = Generator::random_privkey(); 537 | let pubkey = privkey.pubkey().expect("pubkey"); 538 | let pubkey_hash = eth160(pubkey); 539 | 540 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 3)], &mut rng); 541 | let witness = Unpack::>::unpack(&tx.witnesses()).remove(0); 542 | let tx = tx 543 | .as_advanced_builder() 544 | .set_witnesses(vec![ 545 | witness.pack(), 546 | Bytes::new().pack(), 547 | Bytes::from(vec![42]).pack(), 548 | ]) 549 | .build(); 550 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 3); 551 | 552 | // exchange witness position 553 | let witness = Unpack::>::unpack(&tx.witnesses()).remove(0); 554 | let tx = tx 555 | .as_advanced_builder() 556 | .set_witnesses(vec![ 557 | witness.pack(), 558 | Bytes::from(vec![42]).pack(), 559 | Bytes::new().pack(), 560 | ]) 561 | .build(); 562 | 563 | assert_eq!(tx.witnesses().len(), tx.inputs().len()); 564 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 565 | let verify_result = 566 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 567 | assert_error_eq!( 568 | verify_result.unwrap_err(), 569 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 570 | ); 571 | } 572 | 573 | #[test] 574 | fn test_sighash_all_cover_extra_witnesses() { 575 | let mut rng = thread_rng(); 576 | let mut data_loader = DummyDataLoader::new(); 577 | let privkey = Generator::random_privkey(); 578 | let pubkey = privkey.pubkey().expect("pubkey"); 579 | let pubkey_hash = eth160(pubkey); 580 | 581 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 582 | let witness = Unpack::>::unpack(&tx.witnesses()).remove(0); 583 | let tx = tx 584 | .as_advanced_builder() 585 | .set_witnesses(vec![ 586 | witness.pack(), 587 | Bytes::from(vec![42]).pack(), 588 | Bytes::new().pack(), 589 | ]) 590 | .build(); 591 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 3); 592 | assert!(tx.witnesses().len() > tx.inputs().len()); 593 | 594 | // change last witness 595 | let mut witnesses = Unpack::>::unpack(&tx.witnesses()); 596 | let tx = tx 597 | .as_advanced_builder() 598 | .set_witnesses(vec![ 599 | witnesses.remove(0).pack(), 600 | witnesses.remove(1).pack(), 601 | Bytes::from(vec![0]).pack(), 602 | ]) 603 | .build(); 604 | 605 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 606 | let verify_result = 607 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(60000000); 608 | assert_error_eq!( 609 | verify_result.unwrap_err(), 610 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 611 | ); 612 | } 613 | -------------------------------------------------------------------------------- /src/tests/secp256k1_keccak256_sighash_all_acpl_compatibility.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | eth160, sign_tx_by_input_group_keccak256, sign_tx_keccak256, DummyDataLoader, 3 | KECCAK256_ALL_ACPL_BIN, MAX_CYCLES, SECP256K1_DATA_BIN, 4 | }; 5 | use ckb_crypto::secp::{Generator, Privkey}; 6 | use ckb_error::assert_error_eq; 7 | use ckb_script::{ScriptError, TransactionScriptsVerifier}; 8 | use ckb_types::{ 9 | bytes::Bytes, 10 | core::{ 11 | cell::{CellMetaBuilder, ResolvedTransaction}, 12 | Capacity, DepType, ScriptHashType, TransactionBuilder, TransactionView, 13 | }, 14 | packed::{CellDep, CellInput, CellOutput, OutPoint, Script, WitnessArgs, WitnessArgsBuilder}, 15 | prelude::*, 16 | H256, 17 | }; 18 | use rand::{thread_rng, Rng, SeedableRng}; 19 | 20 | use sha3::{Digest, Keccak256}; 21 | 22 | const ERROR_ENCODING: i8 = -2; 23 | const ERROR_WITNESS_SIZE: i8 = -22; 24 | const ERROR_PUBKEY_BLAKE160_HASH: i8 = -31; 25 | 26 | fn gen_tx(dummy: &mut DummyDataLoader, lock_args: Bytes) -> TransactionView { 27 | let mut rng = thread_rng(); 28 | gen_tx_with_grouped_args(dummy, vec![(lock_args, 1)], &mut rng) 29 | } 30 | 31 | fn gen_tx_with_grouped_args( 32 | dummy: &mut DummyDataLoader, 33 | grouped_args: Vec<(Bytes, usize)>, 34 | rng: &mut R, 35 | ) -> TransactionView { 36 | // setup sighash_all dep 37 | let sighash_all_out_point = { 38 | let contract_tx_hash = { 39 | let mut buf = [0u8; 32]; 40 | rng.fill(&mut buf); 41 | buf.pack() 42 | }; 43 | OutPoint::new(contract_tx_hash.clone(), 0) 44 | }; 45 | // dep contract code 46 | let sighash_all_cell = CellOutput::new_builder() 47 | .capacity( 48 | Capacity::bytes(KECCAK256_ALL_ACPL_BIN.len()) 49 | .expect("script capacity") 50 | .pack(), 51 | ) 52 | .build(); 53 | let sighash_all_cell_data_hash = CellOutput::calc_data_hash(&KECCAK256_ALL_ACPL_BIN); 54 | dummy.cells.insert( 55 | sighash_all_out_point.clone(), 56 | (sighash_all_cell, KECCAK256_ALL_ACPL_BIN.clone()), 57 | ); 58 | // setup secp256k1_data dep 59 | let secp256k1_data_out_point = { 60 | let tx_hash = { 61 | let mut buf = [0u8; 32]; 62 | rng.fill(&mut buf); 63 | buf.pack() 64 | }; 65 | OutPoint::new(tx_hash, 0) 66 | }; 67 | let secp256k1_data_cell = CellOutput::new_builder() 68 | .capacity( 69 | Capacity::bytes(SECP256K1_DATA_BIN.len()) 70 | .expect("data capacity") 71 | .pack(), 72 | ) 73 | .build(); 74 | dummy.cells.insert( 75 | secp256k1_data_out_point.clone(), 76 | (secp256k1_data_cell, SECP256K1_DATA_BIN.clone()), 77 | ); 78 | // setup default tx builder 79 | 80 | let block_assembler_code_hash: [u8; 32] = [ 81 | 0x9b, 0xd7, 0xe0, 0x6f, 0x3e, 0xcf, 0x4b, 0xe0, 0xf2, 0xfc, 0xd2, 0x18, 0x8b, 0x23, 0xf1, 82 | 0xb9, 0xfc, 0xc8, 0x8e, 0x5d, 0x4b, 0x65, 0xa8, 0x63, 0x7b, 0x17, 0x72, 0x3b, 0xbd, 0xa3, 83 | 0xcc, 0xe8, 84 | ]; 85 | let lock_script = Script::new_builder() 86 | .code_hash(block_assembler_code_hash.pack()) 87 | .args([0u8; 33].pack()) 88 | .hash_type(ScriptHashType::Type.into()) 89 | .build(); 90 | let dummy_capacity = Capacity::shannons(42); 91 | let mut tx_builder = TransactionBuilder::default() 92 | .cell_dep( 93 | CellDep::new_builder() 94 | .out_point(sighash_all_out_point) 95 | .dep_type(DepType::Code.into()) 96 | .build(), 97 | ) 98 | .cell_dep( 99 | CellDep::new_builder() 100 | .out_point(secp256k1_data_out_point) 101 | .dep_type(DepType::Code.into()) 102 | .build(), 103 | ) 104 | .output( 105 | CellOutput::new_builder() 106 | .capacity(dummy_capacity.pack()) 107 | .lock(lock_script) 108 | .build(), 109 | ) 110 | .output_data(Bytes::new().pack()); 111 | 112 | for (args, inputs_size) in grouped_args { 113 | // setup dummy input unlock script 114 | for _ in 0..inputs_size { 115 | let previous_tx_hash = { 116 | let mut buf = [0u8; 32]; 117 | rng.fill(&mut buf); 118 | buf.pack() 119 | }; 120 | let previous_out_point = OutPoint::new(previous_tx_hash, 0); 121 | let script = Script::new_builder() 122 | .args(args.pack()) 123 | .code_hash(sighash_all_cell_data_hash.clone()) 124 | .hash_type(ScriptHashType::Data.into()) 125 | .build(); 126 | let previous_output_cell = CellOutput::new_builder() 127 | .capacity(dummy_capacity.pack()) 128 | .lock(script) 129 | .build(); 130 | dummy.cells.insert( 131 | previous_out_point.clone(), 132 | (previous_output_cell.clone(), Bytes::new()), 133 | ); 134 | let mut random_extra_witness = [0u8; 32]; 135 | rng.fill(&mut random_extra_witness); 136 | let witness_args = WitnessArgsBuilder::default() 137 | .extra(Bytes::from(random_extra_witness.to_vec()).pack()) 138 | .build(); 139 | tx_builder = tx_builder 140 | .input(CellInput::new(previous_out_point, 0)) 141 | .witness(witness_args.as_bytes().pack()); 142 | } 143 | } 144 | 145 | tx_builder.build() 146 | } 147 | 148 | fn sign_tx_hash(tx: TransactionView, key: &Privkey, tx_hash: &[u8]) -> TransactionView { 149 | // calculate message 150 | // let mut blake2b = ckb_hash::new_blake2b(); 151 | let mut hasher = Keccak256::default(); 152 | let mut message = [0u8; 32]; 153 | // blake2b.update(tx_hash); 154 | // blake2b.finalize(&mut message); 155 | hasher.input(tx_hash); 156 | message.copy_from_slice(&hasher.result()[0..32]); 157 | let message = H256::from(message); 158 | let sig = key.sign_recoverable(&message).expect("sign"); 159 | let witness_args = WitnessArgsBuilder::default() 160 | .lock(Bytes::from(sig.serialize()).pack()) 161 | .build(); 162 | tx.as_advanced_builder() 163 | .set_witnesses(vec![witness_args.as_bytes().pack()]) 164 | .build() 165 | } 166 | 167 | fn build_resolved_tx(data_loader: &DummyDataLoader, tx: &TransactionView) -> ResolvedTransaction { 168 | let resolved_cell_deps = tx 169 | .cell_deps() 170 | .into_iter() 171 | .map(|dep| { 172 | let deps_out_point = dep.clone(); 173 | let (dep_output, dep_data) = 174 | data_loader.cells.get(&deps_out_point.out_point()).unwrap(); 175 | CellMetaBuilder::from_cell_output(dep_output.to_owned(), dep_data.to_owned()) 176 | .out_point(deps_out_point.out_point().clone()) 177 | .build() 178 | }) 179 | .collect(); 180 | 181 | let mut resolved_inputs = Vec::new(); 182 | for i in 0..tx.inputs().len() { 183 | let previous_out_point = tx.inputs().get(i).unwrap().previous_output(); 184 | let (input_output, input_data) = data_loader.cells.get(&previous_out_point).unwrap(); 185 | resolved_inputs.push( 186 | CellMetaBuilder::from_cell_output(input_output.to_owned(), input_data.to_owned()) 187 | .out_point(previous_out_point) 188 | .build(), 189 | ); 190 | } 191 | 192 | ResolvedTransaction { 193 | transaction: tx.clone(), 194 | resolved_cell_deps, 195 | resolved_inputs, 196 | resolved_dep_groups: vec![], 197 | } 198 | } 199 | 200 | #[test] 201 | fn test_keccak_all_unlock() { 202 | let mut data_loader = DummyDataLoader::new(); 203 | let privkey = Generator::random_privkey(); 204 | let pubkey = privkey.pubkey().expect("pubkey"); 205 | let pubkey_hash = eth160(pubkey); 206 | let tx = gen_tx(&mut data_loader, pubkey_hash); 207 | let tx = sign_tx_keccak256(&mut data_loader, tx, &privkey); 208 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 209 | let verify_result = 210 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 211 | verify_result.expect("pass verification"); 212 | } 213 | 214 | #[test] 215 | fn test_sighash_all_with_extra_witness_unlock() { 216 | let mut data_loader = DummyDataLoader::new(); 217 | let privkey = Generator::random_privkey(); 218 | let pubkey = privkey.pubkey().expect("pubkey"); 219 | let pubkey_hash = eth160(pubkey); 220 | let tx = gen_tx(&mut data_loader, pubkey_hash); 221 | let extract_witness = vec![1, 2, 3, 4]; 222 | let tx = tx 223 | .as_advanced_builder() 224 | .set_witnesses(vec![WitnessArgs::new_builder() 225 | .extra(Bytes::from(extract_witness).pack()) 226 | .build() 227 | .as_bytes() 228 | .pack()]) 229 | .build(); 230 | { 231 | let tx = sign_tx_keccak256(&mut data_loader, tx.clone(), &privkey); 232 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 233 | let verify_result = 234 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 235 | verify_result.expect("pass verification"); 236 | } 237 | { 238 | let tx = sign_tx_keccak256(&mut data_loader, tx, &privkey); 239 | let wrong_witness = tx 240 | .witnesses() 241 | .get(0) 242 | .map(|w| { 243 | WitnessArgs::new_unchecked(w.unpack()) 244 | .as_builder() 245 | .extra(Bytes::from(vec![0]).pack()) 246 | .build() 247 | }) 248 | .unwrap(); 249 | let tx = tx 250 | .as_advanced_builder() 251 | .set_witnesses(vec![wrong_witness.as_bytes().pack()]) 252 | .build(); 253 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 254 | let verify_result = 255 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 256 | assert_error_eq!( 257 | verify_result.unwrap_err(), 258 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 259 | ); 260 | } 261 | } 262 | 263 | #[test] 264 | fn test_sighash_all_with_grouped_inputs_unlock() { 265 | let mut rng = thread_rng(); 266 | let mut data_loader = DummyDataLoader::new(); 267 | let privkey = Generator::random_privkey(); 268 | let pubkey = privkey.pubkey().expect("pubkey"); 269 | let pubkey_hash = eth160(pubkey); 270 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 271 | { 272 | let tx = sign_tx_keccak256(&mut data_loader, tx.clone(), &privkey); 273 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 274 | let verify_result = 275 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 276 | verify_result.expect("pass verification"); 277 | } 278 | { 279 | let tx = sign_tx_keccak256(&mut data_loader, tx.clone(), &privkey); 280 | let wrong_witness = tx 281 | .witnesses() 282 | .get(1) 283 | .map(|w| { 284 | WitnessArgs::new_unchecked(w.unpack()) 285 | .as_builder() 286 | .extra(Bytes::from(vec![0]).pack()) 287 | .build() 288 | }) 289 | .unwrap(); 290 | let tx = tx 291 | .as_advanced_builder() 292 | .set_witnesses(vec![ 293 | tx.witnesses().get(0).unwrap(), 294 | wrong_witness.as_bytes().pack(), 295 | ]) 296 | .build(); 297 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 298 | let verify_result = 299 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 300 | assert_error_eq!( 301 | verify_result.unwrap_err(), 302 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 303 | ); 304 | } 305 | } 306 | 307 | #[test] 308 | fn test_sighash_all_with_2_different_inputs_unlock() { 309 | let mut rng = thread_rng(); 310 | let mut data_loader = DummyDataLoader::new(); 311 | // key1 312 | let privkey = Generator::random_privkey(); 313 | let pubkey = privkey.pubkey().expect("pubkey"); 314 | let pubkey_hash = eth160(pubkey); 315 | // key2 316 | let privkey2 = Generator::random_privkey(); 317 | let pubkey2 = privkey2.pubkey().expect("pubkey"); 318 | let pubkey_hash2 = eth160(pubkey2); 319 | 320 | // sign with 2 keys 321 | let tx = gen_tx_with_grouped_args( 322 | &mut data_loader, 323 | vec![(pubkey_hash, 2), (pubkey_hash2, 2)], 324 | &mut rng, 325 | ); 326 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 2); 327 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey2, 2, 2); 328 | 329 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 330 | let verify_result = 331 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 332 | verify_result.expect("pass verification"); 333 | } 334 | 335 | #[test] 336 | fn test_signing_with_wrong_key() { 337 | let mut data_loader = DummyDataLoader::new(); 338 | let privkey = Generator::random_privkey(); 339 | let wrong_privkey = Generator::random_privkey(); 340 | let pubkey = privkey.pubkey().expect("pubkey"); 341 | let pubkey_hash = eth160(pubkey); 342 | let tx = gen_tx(&mut data_loader, pubkey_hash); 343 | let tx = sign_tx_keccak256(&mut data_loader, tx, &wrong_privkey); 344 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 345 | let verify_result = 346 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 347 | assert_error_eq!( 348 | verify_result.unwrap_err(), 349 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 350 | ); 351 | } 352 | 353 | #[test] 354 | fn test_signing_wrong_tx_hash() { 355 | let mut data_loader = DummyDataLoader::new(); 356 | let privkey = Generator::random_privkey(); 357 | let pubkey = privkey.pubkey().expect("pubkey"); 358 | let pubkey_hash = eth160(pubkey); 359 | let tx = gen_tx(&mut data_loader, pubkey_hash); 360 | let tx = { 361 | let mut rand_tx_hash = [0u8; 32]; 362 | let mut rng = thread_rng(); 363 | rng.fill(&mut rand_tx_hash); 364 | sign_tx_hash(tx, &privkey, &rand_tx_hash[..]) 365 | }; 366 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 367 | let verify_result = 368 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 369 | assert_error_eq!( 370 | verify_result.unwrap_err(), 371 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 372 | ); 373 | } 374 | 375 | #[test] 376 | fn test_super_long_witness() { 377 | let mut data_loader = DummyDataLoader::new(); 378 | let privkey = Generator::random_privkey(); 379 | let pubkey = privkey.pubkey().expect("pubkey"); 380 | let pubkey_hash = eth160(pubkey); 381 | let tx = gen_tx(&mut data_loader, pubkey_hash); 382 | let tx_hash = tx.hash(); 383 | 384 | let mut buffer: Vec = vec![]; 385 | buffer.resize(40000, 1); 386 | let super_long_message = Bytes::from(&buffer[..]); 387 | 388 | // let mut blake2b = ckb_hash::new_blake2b(); 389 | let mut hasher = Keccak256::default(); 390 | let mut message = [0u8; 32]; 391 | // blake2b.update(&tx_hash.raw_data()); 392 | // blake2b.update(&super_long_message[..]); 393 | // blake2b.finalize(&mut message); 394 | hasher.input(&tx_hash.raw_data()); 395 | hasher.input(&super_long_message[..]); 396 | message.copy_from_slice(&hasher.result()[0..32]); 397 | let message = H256::from(message); 398 | let sig = privkey.sign_recoverable(&message).expect("sign"); 399 | let witness = WitnessArgs::new_builder() 400 | .lock(Bytes::from(sig.serialize()).pack()) 401 | .extra(super_long_message.pack()) 402 | .build(); 403 | let tx = tx 404 | .as_advanced_builder() 405 | .set_witnesses(vec![witness.as_bytes().pack()]) 406 | .build(); 407 | 408 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 409 | let verify_result = 410 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 411 | assert_error_eq!( 412 | verify_result.unwrap_err(), 413 | ScriptError::ValidationFailure(ERROR_WITNESS_SIZE), 414 | ); 415 | } 416 | 417 | #[test] 418 | fn test_sighash_all_2_in_2_out_cycles() { 419 | const CONSUME_CYCLES: u64 = 7780695; 420 | 421 | let mut data_loader = DummyDataLoader::new(); 422 | let mut generator = Generator::non_crypto_safe_prng(42); 423 | let mut rng = rand::rngs::SmallRng::seed_from_u64(42); 424 | 425 | // key1 426 | let privkey = generator.gen_privkey(); 427 | let pubkey = privkey.pubkey().expect("pubkey"); 428 | let pubkey_hash = eth160(pubkey); 429 | // key2 430 | let privkey2 = generator.gen_privkey(); 431 | let pubkey2 = privkey2.pubkey().expect("pubkey"); 432 | let pubkey_hash2 = eth160(pubkey2); 433 | 434 | // sign with 2 keys 435 | let tx = gen_tx_with_grouped_args( 436 | &mut data_loader, 437 | vec![(pubkey_hash, 1), (pubkey_hash2, 1)], 438 | &mut rng, 439 | ); 440 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 1); 441 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey2, 1, 1); 442 | 443 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 444 | let verify_result = 445 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 446 | let cycles = verify_result.expect("pass verification"); 447 | assert_eq!(CONSUME_CYCLES, cycles) 448 | } 449 | 450 | #[test] 451 | fn test_sighash_all_witness_append_junk_data() { 452 | let mut rng = thread_rng(); 453 | let mut data_loader = DummyDataLoader::new(); 454 | let privkey = Generator::random_privkey(); 455 | let pubkey = privkey.pubkey().expect("pubkey"); 456 | let pubkey_hash = eth160(pubkey); 457 | 458 | // sign with 2 keys 459 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 460 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 2); 461 | let mut witnesses: Vec<_> = Unpack::>::unpack(&tx.witnesses()); 462 | // append junk data to first witness 463 | let mut witness = Vec::new(); 464 | witness.resize(witnesses[0].len(), 0); 465 | witness.copy_from_slice(&witnesses[0]); 466 | witness.push(0); 467 | witnesses[0] = witness.into(); 468 | 469 | let tx = tx 470 | .as_advanced_builder() 471 | .set_witnesses(witnesses.into_iter().map(|w| w.pack()).collect()) 472 | .build(); 473 | 474 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 475 | let verify_result = 476 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 477 | assert_error_eq!( 478 | verify_result.unwrap_err(), 479 | ScriptError::ValidationFailure(ERROR_ENCODING), 480 | ); 481 | } 482 | 483 | #[test] 484 | fn test_sighash_all_witness_args_ambiguity() { 485 | // This test case build tx with WitnessArgs(lock, data, "") 486 | // and try unlock with WitnessArgs(lock, "", data) 487 | // 488 | // this case will fail if contract use a naive function to digest witness. 489 | 490 | let mut rng = thread_rng(); 491 | let mut data_loader = DummyDataLoader::new(); 492 | let privkey = Generator::random_privkey(); 493 | let pubkey = privkey.pubkey().expect("pubkey"); 494 | let pubkey_hash = eth160(pubkey); 495 | 496 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 497 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 2); 498 | let witnesses: Vec<_> = Unpack::>::unpack(&tx.witnesses()); 499 | // move extra data to type_ 500 | let witnesses: Vec<_> = witnesses 501 | .into_iter() 502 | .map(|witness| { 503 | let witness = WitnessArgs::new_unchecked(witness); 504 | let data = witness.extra().clone(); 505 | witness 506 | .as_builder() 507 | .extra(Bytes::new().pack()) 508 | .type_(data) 509 | .build() 510 | }) 511 | .collect(); 512 | 513 | let tx = tx 514 | .as_advanced_builder() 515 | .set_witnesses(witnesses.into_iter().map(|w| w.as_bytes().pack()).collect()) 516 | .build(); 517 | 518 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 519 | let verify_result = 520 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 521 | assert_error_eq!( 522 | verify_result.unwrap_err(), 523 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 524 | ); 525 | } 526 | 527 | #[test] 528 | fn test_sighash_all_witnesses_ambiguity() { 529 | // This test case sign tx with [witness1, "", witness2] 530 | // and try unlock with [witness1, witness2, ""] 531 | // 532 | // this case will fail if contract use a naive function to digest witness. 533 | 534 | let mut rng = thread_rng(); 535 | let mut data_loader = DummyDataLoader::new(); 536 | let privkey = Generator::random_privkey(); 537 | let pubkey = privkey.pubkey().expect("pubkey"); 538 | let pubkey_hash = eth160(pubkey); 539 | 540 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 3)], &mut rng); 541 | let witness = Unpack::>::unpack(&tx.witnesses()).remove(0); 542 | let tx = tx 543 | .as_advanced_builder() 544 | .set_witnesses(vec![ 545 | witness.pack(), 546 | Bytes::new().pack(), 547 | Bytes::from(vec![42]).pack(), 548 | ]) 549 | .build(); 550 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 3); 551 | 552 | // exchange witness position 553 | let witness = Unpack::>::unpack(&tx.witnesses()).remove(0); 554 | let tx = tx 555 | .as_advanced_builder() 556 | .set_witnesses(vec![ 557 | witness.pack(), 558 | Bytes::from(vec![42]).pack(), 559 | Bytes::new().pack(), 560 | ]) 561 | .build(); 562 | 563 | assert_eq!(tx.witnesses().len(), tx.inputs().len()); 564 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 565 | let verify_result = 566 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(MAX_CYCLES); 567 | assert_error_eq!( 568 | verify_result.unwrap_err(), 569 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 570 | ); 571 | } 572 | 573 | #[test] 574 | fn test_sighash_all_cover_extra_witnesses() { 575 | let mut rng = thread_rng(); 576 | let mut data_loader = DummyDataLoader::new(); 577 | let privkey = Generator::random_privkey(); 578 | let pubkey = privkey.pubkey().expect("pubkey"); 579 | let pubkey_hash = eth160(pubkey); 580 | 581 | let tx = gen_tx_with_grouped_args(&mut data_loader, vec![(pubkey_hash, 2)], &mut rng); 582 | let witness = Unpack::>::unpack(&tx.witnesses()).remove(0); 583 | let tx = tx 584 | .as_advanced_builder() 585 | .set_witnesses(vec![ 586 | witness.pack(), 587 | Bytes::from(vec![42]).pack(), 588 | Bytes::new().pack(), 589 | ]) 590 | .build(); 591 | let tx = sign_tx_by_input_group_keccak256(&mut data_loader, tx, &privkey, 0, 3); 592 | assert!(tx.witnesses().len() > tx.inputs().len()); 593 | 594 | // change last witness 595 | let mut witnesses = Unpack::>::unpack(&tx.witnesses()); 596 | let tx = tx 597 | .as_advanced_builder() 598 | .set_witnesses(vec![ 599 | witnesses.remove(0).pack(), 600 | witnesses.remove(1).pack(), 601 | Bytes::from(vec![0]).pack(), 602 | ]) 603 | .build(); 604 | 605 | let resolved_tx = build_resolved_tx(&data_loader, &tx); 606 | let verify_result = 607 | TransactionScriptsVerifier::new(&resolved_tx, &data_loader).verify(60000000); 608 | assert_error_eq!( 609 | verify_result.unwrap_err(), 610 | ScriptError::ValidationFailure(ERROR_PUBKEY_BLAKE160_HASH), 611 | ); 612 | } 613 | --------------------------------------------------------------------------------