Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support doge signer #96

Merged
merged 2 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mode": "pre",
"mode": "exit",
"tag": "alpha",
"initialVersions": {
"@ckb-ccc/ccc": "0.0.15",
Expand Down
8 changes: 8 additions & 0 deletions .changeset/pretty-coins-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@ckb-ccc/utxo-global": patch
"@ckb-ccc/connector": patch
"@ckb-ccc/core": patch
"@ckb-ccc/ccc": patch
---

feat: support doge signer
10 changes: 10 additions & 0 deletions packages/ccc/src/signersController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ export class SignersController {
signerType: ccc.SignerType.BTC,
network: "btcTestnet",
},
{
addressPrefix: "ckb",
signerType: ccc.SignerType.Doge,
network: "doge",
},
{
addressPrefix: "ckt",
signerType: ccc.SignerType.Doge,
network: "dogeTestnet",
},
];

return {
Expand Down
5 changes: 5 additions & 0 deletions packages/connector/src/assets/chains/doge.svg.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/connector/src/assets/chains/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./btc.svg.js";
export * from "./ckb.svg.js";
export * from "./doge.svg.js";
export * from "./eth.svg.js";
export * from "./nostr.svg.js";
2 changes: 2 additions & 0 deletions packages/connector/src/scenes/selecting/signers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { repeat } from "lit/directives/repeat.js";
import {
BTC_SVG,
CKB_SVG,
DOGE_SVG,
ETH_SVG,
NOSTR_SVG,
} from "../../assets/chains/index.js";
Expand All @@ -14,6 +15,7 @@ export function signerTypeToIcon(type: ccc.SignerType): string {
[ccc.SignerType.EVM]: ETH_SVG,
[ccc.SignerType.CKB]: CKB_SVG,
[ccc.SignerType.Nostr]: NOSTR_SVG,
[ccc.SignerType.Doge]: DOGE_SVG,
}[type];
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"type": "git",
"url": "git://github.com/ckb-devrel/ccc.git"
},
"type": "module",
"main": "./dist.commonjs/index.js",
"module": "./dist/index.js",
"exports": {
Expand Down Expand Up @@ -65,6 +66,7 @@
"abort-controller": "^3.0.0",
"bech32": "^2.0.0",
"bitcoinjs-message": "^2.2.0",
"bs58check": "^4.0.0",
"buffer": "^6.0.3",
"cross-fetch": "^4.0.0",
"ethers": "^6.13.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/ckb/transactionLumos.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CellDepLike, CellOutputLike, HexLike, OutPointLike } from "../barrel";
import { CellDepLike, CellOutputLike, OutPointLike } from "../ckb/index.js";
import { HexLike } from "../hex/index.js";

export interface LumosTransactionSkeletonType {
cellDeps: {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ export abstract class Client {
async waitTransaction(
txHash: HexLike,
confirmations: number = 0,
timeout: number = 30000,
timeout: number = 60000,
interval: number = 2000,
): Promise<ClientTransactionResponse | undefined> {
const startTime = Date.now();
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/signer/btc/signerBtc.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { ripemd160 } from "@noble/hashes/ripemd160";
import { sha256 } from "@noble/hashes/sha256";
import { Address } from "../../address/index.js";
import { bytesConcat, bytesFrom } from "../../bytes/index.js";
import { Transaction, TransactionLike, WitnessArgs } from "../../ckb/index.js";
import { KnownScript } from "../../client/index.js";
import { HexLike, hexFrom } from "../../hex/index.js";
import { numToBytes } from "../../num/index.js";
import { Signer, SignerSignType, SignerType } from "../signer/index.js";
import { btcEcdsaPublicKeyHash } from "./verify.js";

/**
* An abstract class extending the Signer class for Bitcoin-like signing operations.
Expand Down Expand Up @@ -62,7 +61,7 @@ export abstract class SignerBtc extends Signer {
*/
async getAddressObjs(): Promise<Address[]> {
const publicKey = await this.getBtcPublicKey();
const hash = ripemd160(sha256(bytesFrom(publicKey)));
const hash = btcEcdsaPublicKeyHash(publicKey);

return [
await Address.fromKnownScript(
Expand Down
33 changes: 31 additions & 2 deletions packages/core/src/signer/btc/verify.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
import { secp256k1 } from "@noble/curves/secp256k1";
import { ripemd160 } from "@noble/hashes/ripemd160";
import { sha256 } from "@noble/hashes/sha256";
import { magicHash } from "bitcoinjs-message";
import { BytesLike, bytesFrom } from "../../bytes/index.js";
import { hexFrom } from "../../hex/index.js";
import bs58check from "bs58check";
import { Bytes, BytesLike, bytesConcat, bytesFrom } from "../../bytes/index.js";
import { Hex, hexFrom } from "../../hex/index.js";

/**
* @public
*/
export function btcEcdsaPublicKeyHash(publicKey: BytesLike): Bytes {
return ripemd160(sha256(bytesFrom(publicKey)));
}

/**
* @public
*/
export function btcP2pkhAddressFromPublicKey(
publicKey: BytesLike,
network: number,
): string {
return bs58check.encode(
bytesConcat([network], btcEcdsaPublicKeyHash(publicKey)),
);
}

/**
* @public
*/
export function btcPublicKeyFromP2pkhAddress(address: string): Hex {
return hexFrom(bs58check.decode(address).slice(1));
}

/**
* @public
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/signer/doge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./signerDoge.js";
export * from "./signerDogeAddressReadonly.js";
export * from "./signerDogePrivateKey.js";
export * from "./verify.js";
116 changes: 116 additions & 0 deletions packages/core/src/signer/doge/signerDoge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import bs58check from "bs58check";
import { Address } from "../../address/index.js";
import { bytesConcat, bytesFrom } from "../../bytes/index.js";
import { Transaction, TransactionLike, WitnessArgs } from "../../ckb/index.js";
import { KnownScript } from "../../client/index.js";
import { hexFrom } from "../../hex/index.js";
import { numToBytes } from "../../num/index.js";
import { Signer, SignerSignType, SignerType } from "../signer/index.js";

/**
* An abstract class extending the Signer class for Dogecoin-like signing operations.
* This class provides methods to get Doge account, public key, and internal address,
* as well as signing transactions.
* @public
*/
export abstract class SignerDoge extends Signer {
get type(): SignerType {
return SignerType.Doge;
}

get signType(): SignerSignType {
return SignerSignType.DogeEcdsa;
}

/**
* Gets the Doge address associated with the signer.
*
* @returns A promise that resolves to a string representing the Doge account.
*/
abstract getDogeAddress(): Promise<string>;

/**
* Gets the internal address, which is the Doge account in this case.
*
* @returns A promise that resolves to a string representing the internal address.
*/
async getInternalAddress(): Promise<string> {
return this.getDogeAddress();
}

/**
* Gets the identity, which is the Doge address in this case.
*
* @returns A promise that resolves to a string representing the identity
*/
async getIdentity(): Promise<string> {
return this.getDogeAddress();
}

/**
* Gets an array of Address objects representing the known script addresses for the signer.
*
* @returns A promise that resolves to an array of Address objects.
*/
async getAddressObjs(): Promise<Address[]> {
const hash = bs58check.decode(await this.getDogeAddress()).slice(1);

return [
await Address.fromKnownScript(
this.client,
KnownScript.OmniLock,
hexFrom([0x05, ...hash, 0x00]),
),
];
}

/**
* prepare a transaction before signing. This method is not implemented and should be overridden by subclasses.
*
* @param txLike - The transaction to prepare, represented as a TransactionLike object.
* @returns A promise that resolves to the prepared Transaction object.
*/
async prepareTransaction(txLike: TransactionLike): Promise<Transaction> {
const tx = Transaction.from(txLike);
const { script } = await this.getRecommendedAddressObj();
await tx.addCellDepsOfKnownScripts(this.client, KnownScript.OmniLock);
await tx.prepareSighashAllWitness(script, 85, this.client);
return tx;
}

/**
* Signs a transaction without modifying it.
*
* @param txLike - The transaction to sign, represented as a TransactionLike object.
* @returns A promise that resolves to a signed Transaction object.
*/
async signOnlyTransaction(txLike: TransactionLike): Promise<Transaction> {
const tx = Transaction.from(txLike);
const { script } = await this.getRecommendedAddressObj();
const info = await tx.getSignHashInfo(script, this.client);
if (!info) {
return tx;
}

const signature = bytesFrom(
await this.signMessageRaw(info.message.slice(2)),
"base64",
);
signature[0] = 31 + ((signature[0] - 27) % 4);

const witness = WitnessArgs.fromBytes(tx.witnesses[info.position]);
witness.lock = hexFrom(
bytesConcat(
numToBytes(5 * 4 + signature.length, 4),
numToBytes(4 * 4, 4),
numToBytes(5 * 4 + signature.length, 4),
numToBytes(5 * 4 + signature.length, 4),
numToBytes(signature.length, 4),
signature,
),
);

tx.setWitnessArgsAt(info.position, witness);
return tx;
}
}
52 changes: 52 additions & 0 deletions packages/core/src/signer/doge/signerDogeAddressReadonly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Client } from "../../client/index.js";
import { SignerDoge } from "./signerDoge.js";

/**
* A class extending SignerDoge that provides read-only access to a Doge address.
* This class does not support signing operations.
* @public
*/
export class SignerDogeAddressReadonly extends SignerDoge {
/**
* Creates an instance of SignerDogeAddressReadonly.
*
* @param client - The client instance used for communication.
* @param address - The Doge address with the signer.
*/
constructor(
client: Client,
private readonly address: string,
) {
super(client);
}

/**
* Connects to the client. This implementation does nothing as the class is read-only.
*
* @returns A promise that resolves when the connection is complete.
*/
async connect(): Promise<void> {}

/**
* Check if the signer is connected.
*
* @returns A promise that resolves the connection status.
*/
async isConnected(): Promise<boolean> {
return true;
}

/**
* Gets the Doge address associated with the signer.
*
* @returns A promise that resolves to a string representing the Doge address.
*
* @example
* ```typescript
* const account = await signer.getDogeAddress(); // Outputs the Doge address
* ```
*/
async getDogeAddress(): Promise<string> {
return this.address;
}
}
Loading
Loading