From d3d912598d800d7eb233cd45d4fb6e6d728e7472 Mon Sep 17 00:00:00 2001 From: painterpuppets Date: Thu, 23 Jun 2022 18:55:17 +0800 Subject: [PATCH] feat: unlock omnilock with rcecellvec --- examples/omni-lock-administrator/index.ts | 260 +++++++++++++--------- examples/omni-lock-administrator/lib.ts | 143 +++--------- 2 files changed, 185 insertions(+), 218 deletions(-) diff --git a/examples/omni-lock-administrator/index.ts b/examples/omni-lock-administrator/index.ts index 45e6ae4db..14667986c 100644 --- a/examples/omni-lock-administrator/index.ts +++ b/examples/omni-lock-administrator/index.ts @@ -14,12 +14,10 @@ import { Cell, core, } from "@ckb-lumos/lumos"; -import * as util from "@nervosnetwork/ckb-sdk-utils"; -import { Blake2b } from "@nervosnetwork/ckb-sdk-utils/lib/crypto/blake2b"; -import { SerializeRCData, SerializeRcLockWitnessLock } from "./generated/omni"; -import { H256, Hasher, SparseMerkleTree } from "sparse-merkle-tree-ts"; -const { deploy } = commons; +import { SerializeRcLockWitnessLock } from "./generated/omni"; +import { generateSingleProof, ProofScheme, ProofMask, buildRcVec, h256ToHex, hexToH256, H256 } from './lib'; const { Reader } = toolkit; +const { deploy } = commons; export const CONFIG = config.createConfig({ PREFIX: "ckt", @@ -67,65 +65,32 @@ const generateSECP256K1Account = (privKey: string): Account => { }; export const alice = generateSECP256K1Account("0xfd686a48908e8caf97723578bf85f746e1e1d8956cb132f6a2e92e7234a2a245"); -export const bob = generateSECP256K1Account("0xfd686a48908e8caf97723578bf85f746e1e1d8956cb132f6a2e92e7234a2a245"); +export const bob = generateSECP256K1Account("0x5368b818f59570b5bc078a6a564f098a191dcb8938d95c413be5065fd6c42d32"); export const rpc = new RPC(CKB_RPC_URL); export const indexer = new Indexer(CKB_INDEXER_URL, CKB_RPC_URL); +export const deployRCEVecCell = async (option: { + from: Address; + fromPrivKey: HexString; + rceCellHashs: HexString[]; +}): Promise => { + const rcData = buildRcVec(option.rceCellHashs); -class Blake2bHasher extends Hasher { - hasher: Blake2b; - - constructor() { - super(); - - this.hasher = util.blake2b(32, null, null, new TextEncoder().encode("ckb-default-hash")); - } - - update(h: H256): this { - this.hasher.update(h); - - return this; - } - - final(): H256 { - return new H256(this.hasher.final("binary") as Uint8Array); - } + return deployRCECell({ from: option.from, fromPrivKey: option.fromPrivKey, rcData }) } -const genSMT = (items: [H256, H256][]) => { - const tree = new SparseMerkleTree(() => new Blake2bHasher()); - - items.map((item) => { - tree.update(item[0], item[1]); - }); - - return tree; -}; - -const buildRCETx = async (fromAddress: Address, root: HexString) => { - const data = SerializeRCData({ - type: "RCRule", - value: { - smt_root: new Reader(root).toArrayBuffer(), - flags: 2, - }, - }); - - return deploy.generateDeployWithTypeIdTx({ - cellProvider: indexer, - scriptBinary: new Uint8Array(data), - fromInfo: fromAddress, - config: CONFIG, - }); -}; - -export const deployRCE = async (option: { +export const deployRCECell = async (option: { from: Address; fromPrivKey: HexString; - root: HexString; + rcData: H256; }): Promise => { - const { txSkeleton, typeId: RCETypeScript } = await buildRCETx(option.from, option.root); + const { txSkeleton, typeId: RCETypeScript } = await deploy.generateDeployWithTypeIdTx({ + cellProvider: indexer, + scriptBinary: new Uint8Array(option.rcData), + fromInfo: option.from, + config: CONFIG, + }) const tx = sealTxSkeleton(txSkeleton, option.fromPrivKey); const txHash = await rpc.send_transaction(tx); @@ -152,6 +117,7 @@ export function toMessages(tx: helpers.TransactionSkeletonType) { // locks you want to sign const signLock = tx.inputs.get(0)?.cell_output.lock!; + console.log(signLock) const messageGroup = commons.createP2PKHMessageGroup(tx, [signLock], { hasher: { @@ -163,21 +129,55 @@ export function toMessages(tx: helpers.TransactionSkeletonType) { return messageGroup[0]; } +interface SmtProof { + mask: HexString, + proof: HexString, +} + export const sealOmnilockTxSkeleton = ( - skeleton: helpers.TransactionSkeletonType, + txSkeleton: helpers.TransactionSkeletonType, privateKey: string, identity: HexString, - proofs: HexString, + proofs: Array, ) => { - const messages = toMessages(skeleton); + const OMNI_SIGNATURE_PLACEHOLDER = new Reader( + "0x" + + "00".repeat( + SerializeRcLockWitnessLock({ + signature: new Reader("0x" + "00".repeat(65)), + rc_identity: { + identity: new Reader(identity).toArrayBuffer(), + proofs: proofs.map(p => ({ + mask: new Reader(p.mask), + proof: new Reader(p.proof), + })) + }, + }).byteLength + ) + ); + + const newWitnessArgs = { lock: OMNI_SIGNATURE_PLACEHOLDER }; + const witness = new Reader( + core.SerializeWitnessArgs(toolkit.normalizers.NormalizeWitnessArgs(newWitnessArgs)) + ).serializeJson(); + + // fill txSkeleton's witness with 0 + for (let i = 0; i < txSkeleton.inputs.toArray().length; i++) { + txSkeleton = txSkeleton.update("witnesses", (witnesses) => witnesses.push(witness)); + } + + const messages = toMessages(txSkeleton); const signature = hd.key.signRecoverable(messages.message, privateKey); const signedWitnessArgs = new Reader( SerializeRcLockWitnessLock({ signature: new Reader(signature), - omni_identity: { - identity: new Reader(identity), - proofs: new Reader(proofs), + rc_identity: { + identity: new Reader(identity).toArrayBuffer(), + proofs: proofs.map(p => ({ + mask: new Reader(p.mask), + proof: new Reader(p.proof), + })), }, }) ); @@ -186,7 +186,7 @@ export const sealOmnilockTxSkeleton = ( core.SerializeWitnessArgs(toolkit.normalizers.NormalizeWitnessArgs({ lock: signedWitnessArgs })) ).serializeJson(); - const txSkeleton = skeleton.update("witnesses", (witnesses) => witnesses.set(0, signedWitness)); + txSkeleton = txSkeleton.update("witnesses", (witnesses) => witnesses.set(0, signedWitness)); return helpers.createTransactionFromSkeleton(txSkeleton); }; @@ -194,7 +194,7 @@ export interface TransferOptions { from: string; to: string; amount: string; - rceCellConfig: config.ScriptConfig; + rceCells: Array; omniIdentity?: { identity: string; proofs: string; @@ -259,40 +259,17 @@ export async function buildTransferByOmnilockAdministrator(options: TransferOpti }, dep_type: CONFIG.SCRIPTS.OMNI_LOCK.DEP_TYPE, }, - // RCE script - { - out_point: { - tx_hash: options.rceCellConfig.TX_HASH, - index: options.rceCellConfig.INDEX, - }, - dep_type: options.rceCellConfig.DEP_TYPE, - } - ) - ); - - const OMNI_SIGNATURE_PLACEHOLDER = new toolkit.Reader( - "0x" + - "00".repeat( - SerializeRcLockWitnessLock({ - signature: new toolkit.Reader("0x" + "00".repeat(65)), - omni_identity: { - identity: new toolkit.Reader("0x" + "00".repeat(65)), - proofs: new toolkit.Reader("0x" + "00".repeat(65)), + // RCE cells + ...options.rceCells.map(cell => ({ + out_point: { + tx_hash: cell.TX_HASH, + index: cell.INDEX, }, - }).byteLength - ) + dep_type: cell.DEP_TYPE, + })) + ) ); - const newWitnessArgs = { lock: OMNI_SIGNATURE_PLACEHOLDER }; - const witness = new toolkit.Reader( - core.SerializeWitnessArgs(toolkit.normalizers.NormalizeWitnessArgs(newWitnessArgs)) - ).serializeJson(); - - // fill txSkeleton's witness with 0 - for (let i = 0; i < tx.inputs.toArray().length; i++) { - tx = tx.update("witnesses", (witnesses) => witnesses.push(witness)); - } - return tx; } @@ -301,29 +278,94 @@ export const generateOmniLockAdministratorAddress = (pubHash: string, typeId: st const lockScript: Script = { code_hash: template.CODE_HASH, hash_type: template.HASH_TYPE, - // omni flag pubkey hash omni lock flags - // chain identity eth addr function flag() - // 00: Nervos 👇 00: owner - // 01: Ethereum 👇 01: administrator - // 👇 👇 👇 args: `0x00${pubHash}01${typeId}`, }; return helpers.encodeToAddress(lockScript, { config: CONFIG }); } -export const generateWLRule = (wllist: Array) => { - const auth_smt_keys = wllist.map(wl => new H256(new Reader(wl).toArrayBuffer())); - let auth_smt_value = H256.zero(); - auth_smt_value[0] = 1; - const smt = genSMT(auth_smt_keys.map(key => [key, auth_smt_value])); +const main = async () => { + const RC_IDENTITY = '0x00' + bob.lockScript.args.substring(2); + const { proof: wlProof, rcData: wlRcData } = generateSingleProof( + ProofScheme.OnWhiteList, + [hexToH256(RC_IDENTITY)] + ) + + const { proof: blProof, rcData: blRcData } = generateSingleProof( + ProofScheme.NotOnBlackList, + [hexToH256(RC_IDENTITY)] + ) + + // const wlRceCellScript = await deployRCECell({ + // from: alice.address, + // fromPrivKey: alice.privKey, + // rcData: wlRcData, + // }); + + // generate by upper ↑ + const wlRceCellScript: config.ScriptConfig = { + CODE_HASH: '0x7ff95669983cb7c54b8cb302f2f202a8d178c1b76f53c0fa76ba03ec97df6042', + HASH_TYPE: 'type', + TX_HASH: '0x584f8105aa6a39bd139a0f2321ea991b5033612f31339f5ddcf9eaf7e097af13', + INDEX: '0x0', + DEP_TYPE: 'code' + } - const proof = smt.merkle_proof(auth_smt_keys); - const compiledProof = proof.compile(auth_smt_keys.map(key => [key, auth_smt_value])); - const compiledProofHex = "0x" + compiledProof.map((x) => x.toString(16).padStart(2, "0")).join(""); + // const blRceCellScript = await deployRCECell({ + // from: alice.address, + // fromPrivKey: alice.privKey, + // rcData: blRcData, + // }); + + // generate by upper ↑ + const blRceCellScript: config.ScriptConfig = { + CODE_HASH: '0x80a3aa808da4baf19728ba98297c3e96f10a6eccba7a10f66e35a8ef0845ab44', + HASH_TYPE: 'type', + TX_HASH: '0xa57484c8627a6c8f1ca47e95e2c1c26315154e2c5e34f0f02f4a9678411c889d', + INDEX: '0x0', + DEP_TYPE: 'code' + } - return { - root: new Reader(smt.root).serializeJson(), - proof: compiledProofHex, + // const RceVecCellScript = await deployRCEVecCell({ + // from: alice.address, + // fromPrivKey: alice.privKey, + // rceCellHashs: [wlRceCellScript.CODE_HASH, blRceCellScript.CODE_HASH], + // }); + + // generate by upper ↑ + const RceVecCellScript: config.ScriptConfig = { + CODE_HASH: '0x23abc4961b1dfecc316f6cbf45e16da19faa50980ac6a8fd81e7df4211069945', + HASH_TYPE: 'type', + TX_HASH: '0x0d65b11ef71dcd7d41d3b3ec345fd41dcbebd555074419afe1511a94f31dc9c2', + INDEX: '0x0', + DEP_TYPE: 'code' } + + const finalCellScript: config.ScriptConfig = RceVecCellScript; + + const smtProofs = [blProof, wlProof].map((proof) => ({ + mask: ProofMask.BothOn, + proof: h256ToHex(proof), + })); + + const TYPE_ID = finalCellScript.CODE_HASH.substring(2); + const aliceOmniAddr = generateOmniLockAdministratorAddress(alice.lockScript.args.substring(2), TYPE_ID); + + const txSkeleton = await buildTransferByOmnilockAdministrator({ + rceCells: [wlRceCellScript, blRceCellScript, finalCellScript], + from: aliceOmniAddr, + to: bob.address, + amount: "10000000000", + }); + + const signedTx = sealOmnilockTxSkeleton( + txSkeleton, + bob.privKey, + RC_IDENTITY, + smtProofs, + ); + const txHash = await rpc.send_transaction(signedTx, "passthrough"); + console.log(txHash) } + +main() \ No newline at end of file diff --git a/examples/omni-lock-administrator/lib.ts b/examples/omni-lock-administrator/lib.ts index a048b1d62..df09ce11e 100644 --- a/examples/omni-lock-administrator/lib.ts +++ b/examples/omni-lock-administrator/lib.ts @@ -1,11 +1,10 @@ -import { toolkit, HexString, Address, config, commons, utils } from "@ckb-lumos/lumos"; -import { CellProvider } from "@ckb-lumos/base"; +import { HexString } from "@ckb-lumos/base"; +import { Reader } from "@ckb-lumos/toolkit"; import { H256, Hasher, SparseMerkleTree } from "sparse-merkle-tree-ts"; import * as util from "@nervosnetwork/ckb-sdk-utils"; import { Blake2b } from "@nervosnetwork/ckb-sdk-utils/lib/crypto/blake2b"; import { SerializeRCData } from "./generated/omni"; -const { Reader } = toolkit; -const { deploy } = commons; +export { H256 }; export const SMT_EXISTING: H256 = new H256([ 1, @@ -82,6 +81,20 @@ export const WHITE_BLACK_LIST_MASK = 0x2; // off(0): not int emergency halt mode export const EMERGENCY_HALT_MODE_MASK = 0x1; +export enum ProofMask { + OnlyInput = "0x01", + OnlyOutput = "0x02", + BothOn = "0x03", +} + +export enum ProofScheme { + OnWhiteList, + NotOnWhiteList, + OnBlackList, + NotOnBlackList, + EmergencyHaltMode, +} + class Blake2bHasher extends Hasher { hasher: Blake2b; @@ -310,7 +323,6 @@ export const buildRcRule = (smtRoot: H256, config: { isBlack?: boolean; isEmerge return 0; })(); - const data = SerializeRCData({ type: "RCRule", value: { @@ -322,114 +334,24 @@ export const buildRcRule = (smtRoot: H256, config: { isBlack?: boolean; isEmerge return new H256(data); }; -export enum RceScheme { - None, - OnWhiteList, - NotOnWhiteList, - OnlyInputOnWhiteList, - OnlyOutputOnWhiteList, - BothOnWhiteList, - OnBlackList, - NotOnBlackList, - BothOn, - EmergencyHaltMode, - OwnerModeForInputType, - OwnerModeForOutputType, -} - - -interface DeployOptions { - config?: config.Config; - feeRate?: bigint; -} - -// first generate N RCE cells with each contained one RCRule -// then collect all these RCE cell hash and create the final RCE cell. -export const generateRceCell = async ( - rcDatas: H256[], - fromInfo: Address, - cellProvider: CellProvider, - options: DeployOptions = {}, -) => { - const cellHashVec = await Promise.all( - rcDatas.map((data) => deploy.generateDeployWithTypeIdTx({ - cellProvider, - scriptBinary: new Uint8Array(data), - fromInfo, - config: options.config, - }) - .then(({ typeId }) => utils.computeScriptHash(typeId)) - ) - ) - - const rceCellContent = SerializeRCData({ +export const buildRcVec = (RCEcellHashs: Array) => { + const data = SerializeRCData({ type: "RCCellVec", - value: cellHashVec.map(cellHash => new Reader(cellHash).toArrayBuffer()), + value: RCEcellHashs.map(cellHash => new Reader(cellHash).toArrayBuffer()), }); - return deploy.generateDeployWithTypeIdTx({ - cellProvider, - scriptBinary: new Uint8Array(rceCellContent), - fromInfo, - config: options.config, - }) -} - - -export const generateProofs = (scheme: RceScheme, hashes: H256[]) => { - if (scheme === RceScheme.BothOn) { - const [proof1, rcData1] = generateSingleProof(RceScheme.OnWhiteList, hashes); - const [proof2, rcData2] = generateSingleProof(RceScheme.OnBlackList, hashes); - return { - proofs: [proof1, proof2], - rcData: [rcData1, rcData2], - proofMasks: [3, 3], - }; - } - - if (scheme === RceScheme.OnlyInputOnWhiteList) { - const [proof1, rcData1] = generateSingleProof(RceScheme.OnWhiteList, hashes); - const [proof2, rcData2] = generateSingleProof(RceScheme.NotOnWhiteList, hashes); - return { - proofs: [proof1, proof2], - rcData: [rcData1, rcData2], - proofMasks: [1, 2], - }; - } - - if (scheme === RceScheme.OnlyOutputOnWhiteList) { - const [proof1, rcData1] = generateSingleProof(RceScheme.NotOnWhiteList, hashes); - const [proof2, rcData2] = generateSingleProof(RceScheme.OnWhiteList, hashes); - return { - proofs: [proof1, proof2], - rcData: [rcData1, rcData2], - proofMasks: [1, 2], - }; - } - - if (scheme === RceScheme.BothOnWhiteList) { - const [proof1, rcData1] = generateSingleProof(RceScheme.OnWhiteList, hashes); - const [proof2, rcData2] = generateSingleProof(RceScheme.OnWhiteList, hashes); - return { - proofs: [proof1, proof2], - rcData: [rcData1, rcData2], - proofMasks: [1, 2], - }; - } - const [proof1, rcData1] = generateSingleProof(scheme, hashes); - return { - proofs: [proof1], - rcData: [rcData1], - proofMasks: [3], - }; + return new H256(data); }; -export const generateSingleProof = (scheme: RceScheme, hashes: H256[]): [H256, H256] => { +export const generateSingleProof = (scheme: ProofScheme, hashes: H256[]): { + proof: H256, + rcData: H256, +} => { const { isBlackList, isEmergencyHalt, smtRoot, proof } = (() => { const isBlackList = false; const isEmergencyHalt = false; - if (scheme === RceScheme.OnWhiteList) { + if (scheme === ProofScheme.OnWhiteList) { const res = buildSmtOnWl(hashes); return { isBlackList, @@ -439,7 +361,7 @@ export const generateSingleProof = (scheme: RceScheme, hashes: H256[]): [H256, H }; } - if (scheme === RceScheme.NotOnWhiteList) { + if (scheme === ProofScheme.NotOnWhiteList) { const res = buildSmtOnWl(hashes, false); return { isBlackList, @@ -449,7 +371,7 @@ export const generateSingleProof = (scheme: RceScheme, hashes: H256[]): [H256, H }; } - if (scheme === RceScheme.OnBlackList) { + if (scheme === ProofScheme.OnBlackList) { const res = buildSmtOnBl(hashes); return { isBlackList: true, @@ -459,7 +381,7 @@ export const generateSingleProof = (scheme: RceScheme, hashes: H256[]): [H256, H }; } - if (scheme === RceScheme.NotOnBlackList) { + if (scheme === ProofScheme.NotOnBlackList) { const res = buildSmtOnBl(hashes, false); return { isBlackList: true, @@ -469,7 +391,7 @@ export const generateSingleProof = (scheme: RceScheme, hashes: H256[]): [H256, H }; } - if (scheme === RceScheme.EmergencyHaltMode) { + if (scheme === ProofScheme.EmergencyHaltMode) { return { isBlackList, isEmergencyHalt: true, @@ -486,7 +408,10 @@ export const generateSingleProof = (scheme: RceScheme, hashes: H256[]): [H256, H }; })(); - return [proof, buildRcRule(smtRoot, { isBlack: isBlackList, isEmergency: isEmergencyHalt })]; + return { + proof, + rcData: buildRcRule(smtRoot, { isBlack: isBlackList, isEmergency: isEmergencyHalt }) + }; }; export const h256ToHex = (h256: H256): HexString => {