Skip to content

Commit

Permalink
feat: supported bitcoin auth in omnilock (#607)
Browse files Browse the repository at this point in the history
  • Loading branch information
homura authored Jan 18, 2024
1 parent 4f29e47 commit b44fb39
Show file tree
Hide file tree
Showing 31 changed files with 1,286 additions and 168 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-feet-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-lumos/debugger": minor
---

feat: update default ckb-debugger version to 0.112
5 changes: 5 additions & 0 deletions .changeset/gold-seals-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-lumos/common-scripts": minor
---

feat: supported bitcoin auth in omnilock
10 changes: 10 additions & 0 deletions examples/misc-nodejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@lumos-example/misc",
"private": true,
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@ckb-lumos/lumos": "canary",
"@ckb-lumos/debugger": "canary"
}
}
64 changes: 64 additions & 0 deletions examples/misc-nodejs/verify-sig-via-debugger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { randomBytes } from "crypto";
import { config, hd, Script } from "@ckb-lumos/lumos";
import { CKBDebuggerDownloader, createTestContext, getDefaultConfig } from "@ckb-lumos/debugger";
import { TransactionSkeleton } from "@ckb-lumos/lumos/helpers";
import { common } from "@ckb-lumos/lumos/common-scripts";
import { blockchain, bytes } from "@ckb-lumos/lumos/codec";
import { computeScriptHash } from "@ckb-lumos/lumos/utils";
import { mockOutPoint } from "@ckb-lumos/debugger/lib/context";

const context = createTestContext(getDefaultConfig());
const lumosConfig = { PREFIX: "ckt", SCRIPTS: context.scriptConfigs };
config.initializeConfig(lumosConfig);

const privateKey = bytes.hexify(randomBytes(32));

async function main() {
await new CKBDebuggerDownloader().downloadIfNotExists();

const txSkeleton = TransactionSkeleton().asMutable();
const lock: Script = {
codeHash: context.scriptConfigs.SECP256K1_BLAKE160.CODE_HASH,
hashType: context.scriptConfigs.SECP256K1_BLAKE160.HASH_TYPE,
args: hd.key.privateKeyToBlake160(privateKey),
};

await common.setupInputCell(txSkeleton, {
cellOutput: { capacity: "0x123123123", lock: lock },
data: "0x",
outPoint: mockOutPoint(),
});

txSkeleton.update("cellDeps", (cellDeps) =>
cellDeps.push({
depType: context.scriptConfigs.SECP256K1_BLAKE160.DEP_TYPE,
outPoint: {
txHash: context.scriptConfigs.SECP256K1_BLAKE160.TX_HASH,
index: context.scriptConfigs.SECP256K1_BLAKE160.INDEX,
},
})
);

common.prepareSigningEntries(txSkeleton, { config: lumosConfig });
const message = txSkeleton.get("signingEntries").get(0)!.message;
const sig = hd.key.signRecoverable(message, privateKey);
txSkeleton.update("witnesses", (witnesses) =>
witnesses.update(0, (witness) => {
const unsigned = blockchain.WitnessArgs.unpack(witness!);
unsigned.lock = sig;
return bytes.hexify(blockchain.WitnessArgs.pack(unsigned));
})
);

const result = await context.executor.execute(txSkeleton, {
scriptHash: computeScriptHash(lock),
scriptGroupType: "lock",
});

console.assert(result.code === 0);

console.log("Cycles consumed", result.cycles);
console.log(result.message);
}

main();
8 changes: 4 additions & 4 deletions examples/omni-lock-metamask/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Lumos Works with MetaMask

Nervos maintains a powerful lock
called [Omni Lock](https://github.com/XuJiandong/docs-bank/blob/master/omni_lock.md) (previously named RC lock), which
can use MetaMask as a signer. This example will show how to use Lumos to send a transaction using Omni lock and MetaMask
called [Omnilock](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md) (previously named RC lock), which
can use MetaMask as a signer. This example will show how to use Lumos to send a transaction using Omnilock and MetaMask

## Quick Start

Expand All @@ -17,6 +17,6 @@ npm start

## Links

- [MetaMask](https://metamask.io/) - A crpto wallet
- [MetaMask](https://metamask.io/) - A cryptocurrency wallet
- [Nervos Faucet](https://faucet.nervos.org/) - Claim Nervos testnet CKB
- [Omni lock](https://github.com/XuJiandong/docs-bank/blob/master/omni_lock.md) - Omni lock intro
- [Omnilock](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md) - Omnilock intro
6 changes: 3 additions & 3 deletions examples/omni-lock-secp256k1-blake160/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Lumos & Omnilock secp256k1_blake160

Nervos maintains a powerful lock
called [Omni Lock](https://github.com/XuJiandong/docs-bank/blob/master/omni_lock.md) (previously named RC lock), which
can use private key as a signer. This example will show how to use Lumos to send a transaction using Omni lock
called [Omnilock](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md) (previously named RC lock), which
can use private key as a signer. This example will show how to use Lumos to send a transaction using Omnilock

## Quick Start

Expand All @@ -17,4 +17,4 @@ npm start
## Links

- [Nervos Faucet](https://faucet.nervos.org/) - Claim Nervos testnet CKB
- [Omni lock](https://github.com/XuJiandong/docs-bank/blob/master/omni_lock.md) - Omni lock intro
- [Omnilock](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md) - Omnilock intro
21 changes: 21 additions & 0 deletions examples/omni-lock-unisat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Lumos Works with UniSat

Nervos maintains a powerful lock
called [Omnilock](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md) (previously named RC lock), which
can use MetaMask as a signer. This example will show how to use Lumos to send a transaction using Omnilock and MetaMask

## Quick Start

> we should [build](..) Lumos project first before we start this example
```
npm run build
cd examples/omni-lock-
npm start
```

## Links

- [MetaMask](https://unisat.io/) - A crypto wallet for Bitcoin
- [Nervos Faucet](https://faucet.nervos.org/) - Claim Nervos testnet CKB
- [Omnilock](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md) - Omnilock intro
13 changes: 13 additions & 0 deletions examples/omni-lock-unisat/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lumos ❤️ UniSat</title>
</head>
<body>
<div id="root"></div>
<script src="index.tsx" type="module"></script>
</body>
</html>
95 changes: 95 additions & 0 deletions examples/omni-lock-unisat/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useEffect, useState } from "react";
import { helpers, Script, config, commons } from "@ckb-lumos/lumos";
import ReactDOM from "react-dom";
import { asyncSleep, capacityOf, unisat, transfer } from "./lib";

// use testnet config
config.initializeConfig(config.predefined.AGGRON4);

const App: React.FC = () => {
const [bitcoinAddr, setBitcoinAddr] = useState("");
const [omniAddr, setOmniAddr] = useState("");
const [omniLock, setOmniLock] = useState<Script>();
const [balance, setBalance] = useState("-");

const [transferAddr, setTransferAddress] = useState("");
const [transferAmount, setTransferAmount] = useState("");

const [isSendingTx, setIsSendingTx] = useState(false);
const [txHash, setTxHash] = useState("");

useEffect(() => {
asyncSleep(300).then(() => {
if (unisat) connectToWallet();
});
}, []);

function connectToWallet() {
unisat
.requestAccounts()
.then(([bitcoinAddr]: string[]) => {
const omniLockScript = commons.omnilock.createOmnilockScript({
auth: { flag: "BITCOIN", content: bitcoinAddr },
});

const omniAddr = helpers.encodeToAddress(omniLockScript);

setBitcoinAddr(bitcoinAddr);
setOmniAddr(omniAddr);
setOmniLock(omniLockScript);

return omniAddr;
})
.then((omniAddr) => capacityOf(omniAddr))
.then((balance) => setBalance(balance.div(10 ** 8).toString() + " CKB"));
}

function onTransfer() {
if (isSendingTx) return;
setIsSendingTx(true);

transfer({ amount: transferAmount, from: omniAddr, to: transferAddr })
.then(setTxHash)
.catch((e) => {
console.log(e);
alert(e.message || JSON.stringify(e));
})
.finally(() => setIsSendingTx(false));
}

if (!unisat) return <div>UniSat is not installed</div>;
if (!bitcoinAddr) return <button onClick={connectToWallet}>Connect to UniSat</button>;

return (
<div>
<ul>
<li>Bitcoin Address: {bitcoinAddr}</li>
<li>Nervos Address(Omni): {omniAddr}</li>
<li>
Current Omni lock script:
<pre>{JSON.stringify(omniLock, null, 2)}</pre>
</li>

<li>Balance: {balance}</li>
</ul>

<div>
<h2>Transfer to</h2>
<label htmlFor="address">Address</label>&nbsp;
<input id="address" type="text" onChange={(e) => setTransferAddress(e.target.value)} placeholder="ckt1..." />
<br />
<label htmlFor="amount">Amount</label>
&nbsp;
<input id="amount" type="text" onChange={(e) => setTransferAmount(e.target.value)} placeholder="shannon" />
<br />
<button onClick={onTransfer} disabled={isSendingTx}>
Transfer
</button>
<p>Tx Hash: {txHash}</p>
</div>
</div>
);
};

const app = document.getElementById("root");
ReactDOM.render(<App />, app);
134 changes: 134 additions & 0 deletions examples/omni-lock-unisat/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { BI, Cell, helpers, Indexer, RPC, config, commons } from "@ckb-lumos/lumos";
import { omnilock } from "@ckb-lumos/lumos/common-scripts";
import { blockchain, bytify, hexify } from "@ckb-lumos/lumos/codec";

const CKB_RPC_URL = "https://testnet.ckb.dev";
const rpc = new RPC(CKB_RPC_URL);
const indexer = new Indexer(CKB_RPC_URL);

declare global {
interface Window {
unisat: omnilock.bitcoin.Provider;
}
}

export const unisat = window.unisat;

export function asyncSleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

interface Options {
from: string;
to: string;
amount: string;
}
const SECP_SIGNATURE_PLACEHOLDER = hexify(
new Uint8Array(
commons.omnilock.OmnilockWitnessLock.pack({
signature: new Uint8Array(65).buffer,
}).byteLength
)
);

export async function transfer(options: Options): Promise<string> {
const CONFIG = config.getConfig();
let tx = helpers.TransactionSkeleton({});
const fromScript = helpers.parseAddress(options.from);
const toScript = helpers.parseAddress(options.to);

// additional 0.001 ckb for tx fee
// the tx fee could calculate by tx size
// this is just a simple example
const neededCapacity = BI.from(options.amount).add(100000);
let collectedSum = BI.from(0);
const collectedCells: Cell[] = [];
const collector = indexer.collector({ lock: fromScript, type: "empty" });
for await (const cell of collector.collect()) {
collectedSum = collectedSum.add(cell.cellOutput.capacity);
collectedCells.push(cell);
if (BI.from(collectedSum).gte(neededCapacity)) break;
}

if (collectedSum.lt(neededCapacity)) {
throw new Error(`Not enough CKB, expected: ${neededCapacity}, actual: ${collectedSum} `);
}

const transferOutput: Cell = {
cellOutput: {
capacity: BI.from(options.amount).toHexString(),
lock: toScript,
},
data: "0x",
};

const changeOutput: Cell = {
cellOutput: {
capacity: collectedSum.sub(neededCapacity).toHexString(),
lock: fromScript,
},
data: "0x",
};

tx = tx.update("inputs", (inputs) => inputs.push(...collectedCells));
tx = tx.update("outputs", (outputs) => outputs.push(transferOutput, changeOutput));
tx = tx.update("cellDeps", (cellDeps) =>
cellDeps.push(
// omni lock dep
{
outPoint: {
txHash: CONFIG.SCRIPTS.OMNILOCK.TX_HASH,
index: CONFIG.SCRIPTS.OMNILOCK.INDEX,
},
depType: CONFIG.SCRIPTS.OMNILOCK.DEP_TYPE,
},
// SECP256K1 lock is depended by omni lock
{
outPoint: {
txHash: CONFIG.SCRIPTS.SECP256K1_BLAKE160.TX_HASH,
index: CONFIG.SCRIPTS.SECP256K1_BLAKE160.INDEX,
},
depType: CONFIG.SCRIPTS.SECP256K1_BLAKE160.DEP_TYPE,
}
)
);

const witness = hexify(blockchain.WitnessArgs.pack({ lock: SECP_SIGNATURE_PLACEHOLDER }));

// fill txSkeleton's witness with placeholder
for (let i = 0; i < tx.inputs.toArray().length; i++) {
tx = tx.update("witnesses", (witnesses) => witnesses.push(witness));
}

tx = commons.omnilock.prepareSigningEntries(tx, { config: CONFIG });

const signedMessage = await omnilock.bitcoin.signMessage(tx.signingEntries.get(0).message, "ecdsa", window.unisat);

const signedWitness = hexify(
blockchain.WitnessArgs.pack({
lock: commons.omnilock.OmnilockWitnessLock.pack({
signature: bytify(signedMessage).buffer,
}),
})
);

tx = tx.update("witnesses", (witnesses) => witnesses.set(0, signedWitness));

const signedTx = helpers.createTransactionFromSkeleton(tx);
const txHash = await rpc.sendTransaction(signedTx, "passthrough");

return txHash;
}

export async function capacityOf(address: string): Promise<BI> {
const collector = indexer.collector({
lock: helpers.parseAddress(address),
});

let balance = BI.from(0);
for await (const cell of collector.collect()) {
balance = balance.add(cell.cellOutput.capacity);
}

return balance;
}
Loading

2 comments on commit b44fb39

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 New canary release: 0.0.0-canary-b44fb39-20240118074526

npm install @ckb-lumos/[email protected]

@vercel
Copy link

@vercel vercel bot commented on b44fb39 Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.