Lay2 - pw-sdk - Build DApps on CKB and Run Them Everywhere

In this post we will walk through some important technical and functional points mentioned in the abstract, and extend some of them into possible usage scenarios in the future.

pw-lib related

As we all know, customizable cryptographic primitive is such a highlight of CKB that we have heard this feature too many times in core team’s keynotes and AMAs. But why this is important? What cryptographic primitives can we deploy on CKB and how should we make use of them? Let us show you some ideas with some libraries we will introduce in pw-lib.

  • Keccak-256 Hash Algorithm

Keccak, sometimes called SHA-3, is the hashing function of Ethereum. In fact, Keccak-256 is a superset of SHA-3, and the finalized NIST SHA-3 is the standard “SHA-3”, which is different with Ethereum’s hash algorithm.

Keccak-256 provides 256-bit hashes. It is used to generate transaction hash, block hash, and even address in Ethereum. In pw-core, Keccak-256 is used to generate hash to be signed and put in witness. But the real indispensability of Keccak-256 in pw-lock, where we need to recover Ethereum address from the public key. The EIP-712 typed data signature also needs Keccak-256 as the only hash algorithm.

  • SHA-256 Hash Algorithm

SHA-256 stands for Secure Hash Algorithm - 256 bit. Although it looks a lot ‘stronger’ than the SHA-3 algorithm mentioned above, it actually belongs to SHA-2 family, and naturally weaker than SHA-3.

The most famous usage of SHA-256 is Bitcoin mining. It is also used to generate Bitcoin address, and widely used as EOS’s hash algorithm. It is necessary for pw-lock to support both Bitcoin wallets and EOS wallets.

  • Secp256r1 (ECC P-256) Asymmetric Encryption Algorithm

Different from hash algorithms which are used to map data of arbitrary size to a fixed bit string in a one-way form, asymmetric encryption algorithms are used for encrypting or digitally signing data.

ECC stands for Elliptic Curve Cryptography, both Secp256k1 and Secp256r1 belong to this algorithm family. Since Bitcoin first adopted Secp256k1 as the signature algorithm, almost all successors have followed this design, and so has CKB. However, the Secp256r1 algorithm, known as P-256, has a wider acceptance in browsers and smart devices. By adopting P-256, we can open a wider range of application scenarios for CKB.

  • More to expect

As the scenario grows, there will be more demand for cryptographic libraries on CKB, such as SHA3-256 for Tron adaptation, and RSA for interacting with secure web applications. Thanks to the RISC-V vm and the cell model of CKB, we can easily integrate those libraries continuously without causing any painful interruption to both developers and users.

pw-lock related

  • Ethereum Signature Support (including EIP-712)
/* secp256k1_keccak256_sighash_all.c */
keccak_init(&sha3_ctx);
unsigned char eth_prefix[28]= {
  0x19, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x20, 0x53,0x69,
  0x67, 0x6e, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61,0x67, 0x65,
  0x3a, 0x0a, 0x33, 0x32
};
keccak_update(&sha3_ctx, eth_prefix, 28);
keccak_update(&sha3_ctx, message, 32);
keccak_final(&sha3_ctx, message);
	
/* Personal hash */
ret = verify_signature(message, lock_bytes, args_bytes_seg.ptr);
if(ret == CKB_SUCCESS){
  return CKB_SUCCESS;
}

/* Typed Data hash */
ret = calculate_typed_data(message, message);
if(ret != CKB_SUCCESS){
  return ret;
}
return verify_signature(message, lock_bytes, args_bytes_seg.ptr)

This is part of the code in pw-lock, which shows the logic to verify the signature in witnesses. Among them, ‘keccak_init’ initializes context for hash calculating, ‘keccak_update’ appends messages to original string, and ‘keccak_final’ finishes the calculation. As we can see, the lock first tries to recover and verify public key from the message as a personal_sign signature. If that fails, then the signature is treated as a typed data signature. Full code: https://github.com/lay2dev/ckb-system-scripts/blob/master/c/secp256k1_keccak256_sighash_all.c

  • How to Support EOS Signature

EOS has a different account model from Ethereum. Firstly, EOS accounts are not generated from public key. They are 12 characters long and can contain the letters a-z and the digits 1–5. In fact, the key-pairs act only as password to unlock the account. Secondly, there can be several different keys associated to one account, with different permissions. For example, EOS account ‘zhixiandaren’ has two permissions (the default two):

Associated public keys for EOS account 'zhixiandaren': 
- active: EOS6P9tLLAQYJbmPzoTEG9vpS6GMHJyWW4bUeppqtAwcUuJpZNXYo
- owner : EOS6P9tLLAQYJbmPzoTEG9vpS6GMHJyWW4bUeppqtAwcUuJpZNXYo

In this case there is only one key used, but normally there should be two different keys. The owner key has the highest authority and should not be used frequently, and common operations such as transfers and votes are handled by the active key.

To support EOS signature, pw-lock should compare the recovered key with the active key, which should be passed to the script in args. To be noted, this is for common EOS accounts, other cases like contract permission (@eos.code) are not included for now.

pw-core related

  • How Can We Map Other Blockchain Address to CKB Address

Let’s first talk about what exactly CKB address stands for. As we all know, if Alice want to send crypto currency to Bob, all she needs to know is Bob’s address, much like sending him an email. But how can Bob actually use the received assets, like login to his email account and forward an email? This comes to the lock script - a script which determines whether you are the right person to unlock the assets, just like a script determines whether Bob has the right password to login to the email account ‘[email protected]’.

Our blockchain account has a pair of keys: private key and public key, and the address is often transformed from the public key (e.g. Ethereum address is the last 20 bytes of the public key). When we send a transaction, we have to create a digital signature with our private key and send it along with the message. When the blockchain verifies your access, the public key is firstly recovered from the signature, and then transformed into an address, which will be compared to the address we declared to own.

For common blockchains who have a fixed lock logic, it’s already enough to confirm ownership with signature and address. But CKB is so flexible that everyone can create his lock script with any logic in it. So besides the ‘address’ part from the public key, there is another part to identify which lock script will be used to verify the ‘address’. Plus some other parts like prefix and checksum, and some transformation, we can finally get a valid CKB address, like this:

args:  /* the 'address' transformed from public key */
b39bbc0b3673c7d36450bc14cfcdad2d559c6c64

code_hash: /* which lock script to use */
9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8

address generated:
ckb1jda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3vumhs9nvu786dj9p0q5elx66t24n3kxgj53qk

Let’s take the address apart to get a clearer view:

ckb1 - the human readable part

jda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3vumhs9nvu786dj9p0q5elx66t2 - the Base32 encoded payload part, payload format: 0x02/0x04 | code_hash | args

4n3kxgj53qks- the checksum

More detailed information about CKB address can be found here: https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0021-ckb-address-format/0021-ckb-address-format.md

Finally, we have understood that a CKB address contains not only information of the public key, but also the lock script intended to be used. Now we can explain why the Ethereum addresses can be mapped to CKB addresses: We created a lock script which has the same logic with the Ethereum’s lock (the code_hash part), and put the Ethereum address in the args. That’s all. Sounds easy isn’t it? Yes, but only on CKB.

  • How to Sign CKB Transactions With Wallets of Other Blockchains

Let’s take MetaMask as an example:

/* personal_sign */
const DefaultSinger = (from, message) =>
  new Promise((resolve, reject) => {
    const params = [message, from]
    const method = 'personal_sign'

    window.web3.currentProvider.sendAsync({ method, params, from },
      function(err, result) {
        err && reject(err)
        result.error && reject(result.error)
        resolve(result.result)
      })
  }
  
/* signTypedData */
const TypedDataSigner = (from, message, { inputCapacity, outputs }) =>
  new Promise((resolve, reject) => {
    const typedData = buildTypedData(inputCapacity, outputs, message)
    const params = [from, typedData]
    const method = 'eth_signTypedData_v4'

    window.web3.currentProvider.sendAsync({ method, params, from },
      function(err, result) {
        err && reject(err)
        resolve(result.result)
      })
  }

Full code: https://github.com/lay2dev/ckb.pw/blob/master/src/services/eth/core.js

Above is the common method to call signing methods in Ethereum web3 environment. The only difference between signing a CKB transaction with an Ethereum transaction is the ‘message’ part. Now we already know that pw-lock is able to recover Ethereum address from the signature with Keccak-256, so the only thing we have to alter is hashing CKB transaction with Keccak-256, and the we just pass it to the signers above. There is something different when it comes to EIP-712 signature, as we have to assemble the message in form of typed data.

2 Likes