Forked from CKB's system scripts, and currently supports signature generated by personalSign and signTypedData from ethereum wallets.
The CKB system script secp256k1_blake160_sighash_all 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.
git submodule init
git submodule update
make install-tools
make all-via-docker
cargo test --all
In view of the fact that EIP712 can show users more intuitive and safe information, pw-lock preferentially supports EIP712 related signature method eth_signTypedData_v4. 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 signature currently supported by most Ethereum wallets.
pw-lock script provides two signature verification methods: eth_personalSign and eth_signTypedData_v4.
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.
// pseudo code, the actual code is written in C language
const newHash = hashPersonalMessage(hash)
/*
hashPersonalMessage = function(message: Buffer): Buffer {
const prefix = Buffer.from(
`\u0019Ethereum Signed Message:\n${message.length.toString()}`,
'utf-8',
)
return keccak(Buffer.concat([prefix, message]))
}
*/
const pubkey = secp256k1_ecdsa_recover(signature, newHash)
if (pubkey.slice(12, 32) === lock.args){
return 0;
}
- Use the ECDSA_RECOVER algorithm to calculate the 32-byte pubkey from the personalHash and signature.
- Check if the last 20 bytes of pubkey are equal to lock args (that is, Ethereum address).
The hash calculation process is the same as the CKB system lock script, except that blake2b is replaced by keccak256.
// pseudo code, the actual code is written in C language
const newHash = hashPersonalMessage(hash)
const typedData = {
domain: {
chainId: 1,
name: 'ckb.pw',
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
version: '1'
},
message: {
hash:
'0x545529d4464064d8394c557afb06f489e7044a63984c6113385431d93dcffa1b',
fee: '0.00100000CKB',
'input-sum': '100.00000000CKB',
to: [
{
address: 'ckb1qyqv4yga3pgw2h92hcnur7lepdfzmvg8wj7qwstnwm',
amount: '100.00000000CKB'
},
{
address:
'ckb1qftyhqxwuxdzp5zk4rctscnrr6stjrmfjdx54v05q8t3ad3493m6mhcekrn0vk575h44ql9ry53z3gzhtc2exudxcyg',
amount: '799.99800000CKB'
}
]
},
primaryType: 'CKBTransaction',
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
],
CKBTransaction: [
{ name: 'hash', type: 'bytes32' },
{ name: 'fee', type: 'string' },
{ name: 'input-sum', type: 'string' },
{ name: 'to', type: 'Output[]' }
],
Output: [
{ name: 'address', type: 'string' },
{ name: 'amount', type: 'string' }
]
}
}
typedData.message.hash = newHash
typedData.message['input-sum'] = total_input_amount(tx)
typedData.message.fee = total_input_amount(tx) - total_output_amount(tx)
typedData.message.to = extractTxOutputsInfo(tx)
According to the CKB transaction information, input-sum / fee / to related informations are calculated and assigned to the corresponding attribute of typedData.
// pseudo code, the actual code is written in C language
const sigUtil = require('eth-sig-util')
const typedHash = sigUtil.TypedDataUtils.sign(typedData)
const pubkey = secp256k1_ecdsa_recover(signature, typedHash)
if( pubkey.slice(12,32) === lock.args){
return 0;
}
- Calculate typedHash by typedData, and use ECDSA_RECOVER algorithm to calculate 32-byte pubkey from typedHash and signature.
- Check if the last 20 bytes of pubkey are equal to lock args (that is, Ethereum address).
pw-lock also integrates the features of anyone-can-pay, which is consistent with the official anyone-can-pay