Skip to content

Commit

Permalink
feat: move deprecate p2sh from 0.23 to 0.24 (#706)
Browse files Browse the repository at this point in the history
  • Loading branch information
homura authored Jun 6, 2024
1 parent 4110f0d commit f3acc1f
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 156 deletions.
5 changes: 0 additions & 5 deletions .changeset/brave-dragons-retire.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/cyan-geese-relate.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/olive-waves-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-lumos/common-scripts": minor
---

**BREAKING CHANGE**: revert #682 that deprecate omnilock p2sh and supports btc testnet addr
1 change: 0 additions & 1 deletion .eslintrc.next.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ module.exports = {
-1, // index -1 is not found
0, // first element of an array
1, // common for i + 1 in a loop
2, // many .slice(2) since the '0x' prefix should be removed while calling 3rd-party library
16, // toString(16)
1000, // second to millisecond
],
Expand Down
81 changes: 11 additions & 70 deletions packages/common-scripts/src/omnilock-bitcoin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// TODO the magic number eslint will be resolved in 0.24 by recovering https://github.com/ckb-js/lumos/pull/682
/*eslint-disable @typescript-eslint/no-magic-numbers*/

import { bytes, BytesLike } from "@ckb-lumos/codec";
import { bech32 } from "bech32";
import bs58 from "bs58";
Expand All @@ -7,14 +10,10 @@ const BTC_PREFIX = "CKB (Bitcoin Layer) transaction: 0x";

/**
* Decode bitcoin address to public key hash in bytes
* @deprecated please migrate to {@link parseAddressToPublicKeyHash}
* @see https://en.bitcoin.it/wiki/List_of_address_prefixes
* @param address
*/
export function decodeAddress(address: string): ArrayLike<number> {
const btcAddressFlagSize = 1;
const hashSize = 20;

try {
// Bech32
if (address.startsWith("bc1q")) {
Expand All @@ -23,16 +22,12 @@ export function decodeAddress(address: string): ArrayLike<number> {

// P2PKH
if (address.startsWith("1")) {
return bs58
.decode(address)
.slice(btcAddressFlagSize, btcAddressFlagSize + hashSize);
return bs58.decode(address).slice(1, 21);
}

// P2SH
if (address.startsWith("3")) {
return bs58
.decode(address)
.slice(btcAddressFlagSize, btcAddressFlagSize + hashSize);
return bs58.decode(address).slice(1, 21);
}
} catch {
// https://bitcoin.design/guide/glossary/address/#taproot-address---p2tr
Expand All @@ -46,32 +41,6 @@ export function decodeAddress(address: string): ArrayLike<number> {
);
}

export function parseAddressToPublicKeyHash(
address: string
): ArrayLike<number> {
try {
// Bech32
if (isP2wpkhAddress(address)) {
return bech32.fromWords(bech32.decode(address).words.slice(1));
}

// P2PKH
if (isP2pkhAddress(address)) {
const networkSize = 1;
const pubkeyHashSize = 20;
// public key hash
// a P2PKH address is composed of network(1 byte) + pubkey hash(20 bytes)
return bs58
.decode(address)
.slice(networkSize, networkSize + pubkeyHashSize);
}
} catch {
// do nothing here, throw an error below
}

throw new Error("Only supports Native Segwit(P2WPKH) and Legacy(P2PKH)");
}

export interface Provider {
requestAccounts(): Promise<string[]>;
signMessage(message: string, type?: "ecdsa"): Promise<string>;
Expand Down Expand Up @@ -113,58 +82,30 @@ export async function signMessage(
const signature = bytes.bytify(base64ToHex(signatureBase64));

const address = accounts[0];
/* eslint-disable @typescript-eslint/no-magic-numbers */

// a secp256k1 private key can be used to sign various types of messages
// the first byte of signature used as a recovery id to identify the type of message
// https://github.com/XuJiandong/omnilock/blob/4e9fdb6ca78637651c8145bb7c5b82b4591332fb/c/ckb_identity.h#L249-L266
if (isP2wpkhAddress(address)) {
if (address.startsWith("bc1q")) {
signature[0] = 39 + ((signature[0] - 27) % 4);
} else if (isP2shAddress(address)) {
} else if (address.startsWith("3")) {
signature[0] = 35 + ((signature[0] - 27) % 4);
} else if (isP2pkhAddress(address)) {
} else if (address.startsWith("1")) {
signature[0] = 31 + ((signature[0] - 27) % 4);
} else {
throw new Error(
`Unsupported bitcoin address ${address}. Only supports SegWit, P2SH-P2WPKH, P2PKH`
`Unsupported bitcoin address ${address}, only 1...(P2PKH) 3...(P2SH), and bc1...(Bech32) are supported.`
);
}

/* eslint-enable @typescript-eslint/no-magic-numbers */

return bytes.hexify(signature);
}

function base64ToHex(str: string) {
const raw = atob(str);
let result = "";
for (let i = 0; i < raw.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
result += raw.charCodeAt(i).toString(16).padStart(2, "0");
const hex = raw.charCodeAt(i).toString(16);
result += hex.length === 2 ? hex : "0" + hex;
}
return "0x" + result;
}

/* https://en.bitcoin.it/wiki/List_of_address_prefixes */

function isP2wpkhAddress(address: string): boolean {
return (
address.startsWith("bc1") || // mainnet
address.startsWith("tb1") // testnet
);
}

function isP2shAddress(address: string): boolean {
return (
address.startsWith("3") || // mainnet
address.startsWith("2") // testnet
);
}

function isP2pkhAddress(address: string): boolean {
return (
address.startsWith("1") || // mainnet
address.startsWith("m") || // testnet
address.startsWith("n") // testnet
);
}
60 changes: 3 additions & 57 deletions packages/common-scripts/src/omnilock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,8 @@ const SECP256K1_SIGNATURE_PLACEHOLDER_LENGTH = 65;
const ED25519_SIGNATURE_PLACEHOLDER_LENGTH = 96;

/**
* Create an Omnilock script based on other networks' wallet
* @deprecated please migrate to {@link createSimplePublicKeyBasedOmnilockScript}
* @see https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md
* only support ETHEREUM and SECP256K1_BLAKE160 mode currently
* refer to: @link https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md omnilock
* @param omnilockInfo
* @param options
* @returns
Expand All @@ -137,58 +136,6 @@ export function createOmnilockScript(
): Script {
const config = options?.config || getConfig();
const omnilockConfig = config.SCRIPTS.OMNILOCK;

if (!omnilockConfig) {
throw new Error("OMNILOCK script config not found.");
}

const defaultOmnilockArgs = 0b00000000;
const omnilockArgs = [defaultOmnilockArgs];

if (omnilockInfo.auth.flag === "BITCOIN") {
return {
codeHash: omnilockConfig.CODE_HASH,
hashType: omnilockConfig.HASH_TYPE,
args: bytes.hexify(
bytes.concat(
[IdentityFlagsType.IdentityFlagsBitcoin],
bitcoin.decodeAddress(omnilockInfo.auth.content),
omnilockArgs
)
),
};
}

return createSimplePublicKeyBasedOmnilockScript(omnilockInfo, options);
}

/**
* Create an Omnilock script based on other networks' wallet
* @see https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0042-omnilock/0042-omnilock.md
* @param omnilockInfo
* @param options
* @returns
* @example
* // create an omnilock to work with MetaMask wallet
* createOmnilockScript({
* auth: {
* flag: "ETHEREUM",
* content: "an ethereum address here",
* }, { config })
* // or we can create an omnilock to work with UniSat wallet
* createOmnilockScript({
* auth: {
* flag: "BITCOIN",
* content: "a bitcoin address here",
* }
* }, {config})
*/
export function createSimplePublicKeyBasedOmnilockScript(
omnilockInfo: OmnilockInfo,
options?: Options
): Script {
const config = options?.config || getConfig();
const omnilockConfig = config.SCRIPTS.OMNILOCK;
if (!omnilockConfig) {
throw new Error("OMNILOCK script config not found.");
}
Expand Down Expand Up @@ -229,7 +176,7 @@ export function createSimplePublicKeyBasedOmnilockScript(
return bytes.hexify(
bytes.concat(
[IdentityFlagsType.IdentityFlagsBitcoin],
bitcoin.parseAddressToPublicKeyHash(omnilockInfo.auth.content),
bitcoin.decodeAddress(omnilockInfo.auth.content),
omnilockArgs
)
);
Expand Down Expand Up @@ -506,5 +453,4 @@ export default {
CellCollector,
OmnilockWitnessLock,
createOmnilockScript,
createSimplePublicKeyBasedOmnilockScript,
};
28 changes: 10 additions & 18 deletions packages/common-scripts/tests/omnilock-bitcoin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import { blockchain, utils } from "@ckb-lumos/base";
import { bytes } from "@ckb-lumos/codec";
import { common } from "../src";
import { mockOutPoint } from "@ckb-lumos/debugger/lib/context";
import {
createSimplePublicKeyBasedOmnilockScript,
OmnilockWitnessLock,
} from "../src/omnilock";
import { createOmnilockScript, OmnilockWitnessLock } from "../src/omnilock";
import { address, AddressType, core, keyring } from "@unisat/wallet-sdk";
import { NetworkType } from "@unisat/wallet-sdk/lib/network";
import { Provider, signMessage } from "../src/omnilock-bitcoin";
Expand All @@ -35,21 +32,15 @@ test.serial("Omnilock#Bitcoin P2PKH", async (t) => {
t.is(result.code, 0, result.message);
});

test.serial("Omnilock#Bitcoin P2PKH Testnet", async (t) => {
const { provider } = makeProvider(AddressType.P2PKH, NetworkType.TESTNET);
const result = await execute(provider);
t.is(result.code, 0, result.message);
});

test.serial("Omnilock#Bitcoin P2WPKH", async (t) => {
const { provider } = makeProvider(AddressType.P2WPKH);
const result = await execute(provider);

t.is(result.code, 0, result.message);
});

test.serial("Omnilock#Bitcoin P2WPKH Testnet", async (t) => {
const { provider } = makeProvider(AddressType.P2WPKH, NetworkType.TESTNET);
test.serial("Omnilock#Bitcoin P2SH_P2WPKH", async (t) => {
const { provider } = makeProvider(AddressType.P2SH_P2WPKH);
const result = await execute(provider);

t.is(result.code, 0, result.message);
Expand Down Expand Up @@ -79,18 +70,19 @@ async function execute(provider: Provider) {
});
}

function makeProvider(
addressType: AddressType,
network: NetworkType = NetworkType.MAINNET
): {
function makeProvider(addressType: AddressType): {
provider: Provider;
pair: core.ECPairInterface;
keyring: SimpleKeyring;
} {
const pair = core.ECPair.makeRandom();
const ring = new keyring.SimpleKeyring([pair.privateKey!.toString("hex")]);
const publicKey = pair.publicKey.toString("hex");
const addr = address.publicKeyToAddress(publicKey, addressType, network);
const addr = address.publicKeyToAddress(
publicKey,
addressType,
NetworkType.MAINNET
);

return {
pair,
Expand All @@ -106,7 +98,7 @@ function makeProvider(
async function setupTxSkeleton(addr: string) {
const txSkeleton = TransactionSkeleton().asMutable();

const lock = createSimplePublicKeyBasedOmnilockScript(
const lock = createOmnilockScript(
{ auth: { flag: "BITCOIN", content: addr } },
{ config: managerConfig }
);
Expand Down

1 comment on commit f3acc1f

@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-f3acc1f-20240606024131

npm install @ckb-lumos/[email protected]

Please sign in to comment.