Understanding CKB Lock Value Relationships

By Jordan Mack

In CKB smart contract development, it is important to understand how different lock-related values connect to each other. These values form a chain of cryptographic relationships that are used throughout smart contract development for purposes such as ownership verification, address generation, and cross-referencing between different scripts.

The Value Derivation Chain

Here's how all lock-related values connect in CKB at a high level:

Private Keysecp256k1 elliptic curvePublic Key
Public Keyblake160 (ckbhash + truncate)Lock Arg
Lock Argadd code_hash + hash_typeLock Script
Lock Scriptckbhash (molecule serialized)Lock Hash
Lock Scriptbech32m encodingAddress
Starting ValueTransformationResulting Value

Understanding Each Value

Below we will explain each of the values and their relationships with each other in more detail. On CKB you can use any cryptographic algorithm you want, but most wallets rely on secp256k1 because that is what is used for the fallback lock secp256k1-blake160-sighash-all. Our descriptions and examples will all assume this lock.

Private Key (32 bytes)

The chain starts with a secret private key that is never shared but used to prove ownership. This is your fundamental credential for controlling CKB cells and assets.

// Private keys are represented as hex strings in CCC.
const privateKey = "0xd00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc";

Why it matters: Your private key is the root of all ownership verification in CKB. During transaction validation, you use this key to create digital signatures that prove you control specific cells without revealing the key itself.

Further Learning:

Public Key (33 bytes)

The public key is derived from the private key using secp256k1 elliptic curve cryptography. This is the shareable counterpart to your private key that enables others to verify your signatures.

import {SignerCkbPrivateKey, ccc} from "@ckb-ccc/core";

// Initialize CCC client (testnet example).
const client = new ccc.ClientPublicTestnet();

// Create a signer from the private key.
const signer = new SignerCkbPrivateKey(client, privateKey);
const publicKey = signer.publicKey;
// Example: 0x03fe6c6d09d1a0f70255cddf25c5ed57d41b5c08822ae710dc10f8c88290e0acdf (33 bytes).

// Or derive manually using secp256k1.
// const secp256k1 = require('secp256k1');
// const publicKey = "0x" + Buffer.from(secp256k1.publicKeyCreate(Buffer.from(privateKey.slice(2), 'hex'), true)).toString('hex');

Why it matters: Your public key enables signature verification without exposing your private key. It's also the starting point for generating your unique account identifier (lock arg) that distinguishes your cells from all other users.

Further Learning:

Lock Arg (20 bytes)

The lock arg is generated by applying ckbhash to the public key and taking the first 20 bytes (blake160). This is the same length as both Bitcoin and Ethereum.

import {hashCkb, ccc} from "@ckb-ccc/core";

// Derive from public key to show the transformation.
const lockArg = hashCkb(publicKey).slice(0, 42);
// Example: "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" (20 bytes).

// Or get it from an address object.
// const addressObj = await signer.getAddressObjSecp256k1();
// const lockArg = addressObj.script.args;

Why it matters: This value uniquely identifies your "account" when placed in the args field of the secp256k1-blake160-sighash-all lock script. The args value is how the code knows who owns the cell. It serves as an identifier for the basis of authentication, and also a label on cells that can be searched for when building transactions.

Further Learning:

Lock Script (Structure)

The complete script structure that defines cell ownership. This identifies the script code that is executed with your unique identifier to create a lock that is only accessible with your private key.

import {Script, ccc} from "@ckb-ccc/core";

// Create a lock script using CCC.
const lockScript = ccc.Script.from({
	codeHash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
	hashType: "type",
	args: "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7"
});

// Or get it from an address object.
// const addressObj = await signer.getAddressObjSecp256k1();
// const lockScript = addressObj.script;

Key components:

  • codeHash: The hash that identifies the script code to execute.
  • hashType: Used to indicate how to interpret the code hash.
  • args: Your unique identifier (the lock arg derived from your public key).

Why it matters: A lock script is a structure that defines ownership rules for cells. It specifies script code (smart contract) that will execute to validate ownership and arguments provided to the code (lock arg) that determine who can access it.

Further Learning:

Lock Hash (32 bytes)

A unique identifier for the complete lock script structure, calculated by hashing the script structure after being serialized with molecule.

import {hashCkb, ccc} from "@ckb-ccc/core";

// Manual calculation to show the transformation.
const lockHash = hashCkb(lockScript.toBytes());
// Example: "0x32e555f3ff8e135cece1351a6a2971518392c1e30375c1e006ad0ce8eac07947".

// Or use the built-in method (more common).
// const lockHash = lockScript.hash();
// This performs: hashCkb(molecule_encode(lockScript)).

Why it matters: A lock script is a structure comprised of a code hash, hash type, and lock args. This complete component structure is what determines ownership of cells, but it's not easily referenced since it is three long fields. By serializing and hashing the lock script into a lock hash we end up with a space-efficient identifier that can be easily referenced by smart contracts.

Further Learning:

Address (Human-Readable)

The single line representation of your lock script that encodes all the data about the lock script structure in a human-readable format that's easy to share and has checksum validation to prevent mistakes.

import {ccc} from "@ckb-ccc/core";

// Create address from lock script to show the transformation.
const address = ccc.Address.fromScript(lockScript, client).toString();
// Example: "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwgx292hnvmn68xf779vmzrshpmm6epn4c0cgwga".

// Or get it directly from the signer (more common).
// const address = (await signer.getAddressObjSecp256k1()).toString();

Why it matters: Addresses provide a standardized way to share the complete lock script structure in a way that is human-readable and also reversible. A lock hash is similar in that it provides a more concise way to reference a lock script, but since it uses one-way hashing the full lock script structure cannot be extracted. When transferring funds to someone the full lock script is needed, which is why addresses must be used instead of lock hashes.

Further Learning:

Acquiring Values with CCC

Here's how to obtain each lock-related value using CCC as the library was designed to be used. Rather than manually calculating each transformation, CCC provides high-level methods that handle the derivation chain internally:

import {SignerCkbPrivateKey, Script, ccc} from "@ckb-ccc/core";

// Initialize CCC client.
const client = new ccc.ClientPublicTestnet(); // Use ClientPublicMainnet() for mainnet.

// 1. Start with a private key.
const privateKey = "0xd00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc";

// Create signer from private key.
const signer = new SignerCkbPrivateKey(client, privateKey);

// 2. Get public key from signer.
const publicKey = signer.publicKey;

// Get complete address object (contains all derived values).
const addressObj = await signer.getAddressObjSecp256k1();

// 3. Extract lock arg from address object.
const lockArg = addressObj.script.args;

// 4. Extract lock script from address object.
const lockScript = addressObj.script;

// 5. Calculate lock hash from lock script.
const lockHash = lockScript.hash();

// 6. Get human-readable address.
const address = addressObj.toString();

console.log("Lock Value Chain:");
console.log(`1. Private Key: ${privateKey}`);
console.log(`2. Public Key: ${publicKey}`);
console.log(`3. Lock Arg: ${lockArg}`);
console.log(`4. Lock Script:`, lockScript);
console.log(`5. Lock Hash: ${lockHash}`);
console.log(`6. Address: ${address}`);

For a complete working example with both CCC and Lumos implementations, see the Lock Value Relationships Demo (GitHub).

Further Learning

Additional resources and learning materials are available for those interested in deepening their understanding of CKB development.