diff --git a/README.md b/README.md index 34c58fca2..47c8478d1 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,14 @@ Then an authz example website will be created and users can take a look how sign --- +### Bitcoin Network + +| Feature | Package | +| ---------------- | ------------------------------------------------------ | +| **Transactions** | [@interchainjs/bitcoin](https://docs.hyperweb.io/interchain-js/networks/bitcoin) | + +--- + ## Interchain JavaScript Stack ⚛️ A unified toolkit for building applications and smart contracts in the Interchain ecosystem diff --git a/docs/advanced/_meta.json b/docs/advanced/_meta.json index a526c8cd8..1c420ee4d 100644 --- a/docs/advanced/_meta.json +++ b/docs/advanced/_meta.json @@ -4,5 +4,6 @@ "migration-from-cosmjs": "Migration from CosmJS", "signer": "Signer", "tutorial": "Tutorial", - "wallet": "Wallet" -} \ No newline at end of file + "wallet": "Wallet", + "add-network": "Add a Network" +} diff --git a/docs/advanced/add-network.md b/docs/advanced/add-network.md new file mode 100644 index 000000000..31b9615dd --- /dev/null +++ b/docs/advanced/add-network.md @@ -0,0 +1,10 @@ +# Adding a New Network + +This guide outlines the basic steps to integrate a new blockchain network with InterchainJS. A network package exposes signers and account utilities that follow the interfaces defined in `@interchainjs/types`. + +1. **Create a new package** under `networks/` with its own `package.json`, `tsconfig.json` and `src` folder. Export your signers and accounts from an `index.ts` file. +2. **Implement signers** that perform the necessary cryptographic signing. Signers typically wrap private keys or wallet providers and expose methods like `sign` or `send`. +3. **Provide account helpers** to derive addresses from public keys. Reuse hashing and encoding utilities from `@interchainjs/crypto` and `@interchainjs/encoding`. +4. **Document your network** in `docs/networks/` so users can install and use it. + +Existing networks such as [Cosmos](./../networks/cosmos/index.mdx) and [Ethereum](./../networks/ethereum/index.mdx) can serve as references for structuring your package. diff --git a/docs/index.mdx b/docs/index.mdx index 34c58fca2..47c8478d1 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -246,6 +246,14 @@ Then an authz example website will be created and users can take a look how sign --- +### Bitcoin Network + +| Feature | Package | +| ---------------- | ------------------------------------------------------ | +| **Transactions** | [@interchainjs/bitcoin](https://docs.hyperweb.io/interchain-js/networks/bitcoin) | + +--- + ## Interchain JavaScript Stack ⚛️ A unified toolkit for building applications and smart contracts in the Interchain ecosystem diff --git a/docs/networks/_meta.json b/docs/networks/_meta.json index b87d48ae8..0312371d7 100644 --- a/docs/networks/_meta.json +++ b/docs/networks/_meta.json @@ -1,5 +1,6 @@ { "cosmos": "Cosmos", "ethereum": "Ethereum", - "injective": "Injective" -} \ No newline at end of file + "injective": "Injective", + "bitcoin": "Bitcoin" +} diff --git a/docs/networks/bitcoin/_meta.json b/docs/networks/bitcoin/_meta.json new file mode 100644 index 000000000..ca0a2a6cb --- /dev/null +++ b/docs/networks/bitcoin/_meta.json @@ -0,0 +1,3 @@ +{ + "index": "Overview" +} diff --git a/docs/networks/bitcoin/index.mdx b/docs/networks/bitcoin/index.mdx new file mode 100644 index 000000000..7836b8ede --- /dev/null +++ b/docs/networks/bitcoin/index.mdx @@ -0,0 +1,31 @@ +# @interchainjs/bitcoin + +Transaction signing and account utilities for the Bitcoin network. + +## Usage + +```sh +npm install @interchainjs/bitcoin +``` + +```ts +import { SignerFromPrivateKey } from '@interchainjs/bitcoin' + +const signer = new SignerFromPrivateKey(privateKeyHex) +const address = signer.getAddress() +``` + +## Implementations + +- **SignerFromPrivateKey** from `@interchainjs/bitcoin/signers/SignerFromPrivateKey` +- **BitcoinAccount** from `@interchainjs/bitcoin/accounts/bitcoin-account` + +## Credits + +🛠 Built by Hyperweb (formerly Cosmology) — if you like our tools, please check out and contribute to [our github ⚛️](https://github.com/hyperweb-io) + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. diff --git a/networks/bitcoin/README.md b/networks/bitcoin/README.md new file mode 100644 index 000000000..8c25b58ab --- /dev/null +++ b/networks/bitcoin/README.md @@ -0,0 +1,32 @@ +# @interchainjs/bitcoin + +Transaction signing and account utilities for the Bitcoin network. + +## Usage + +```sh +npm install @interchainjs/bitcoin +``` + +```ts +import { SignerFromPrivateKey } from '@interchainjs/bitcoin' + +const signer = new SignerFromPrivateKey(privateKeyHex) +const address = signer.getAddress() +``` + +## Implementations + +- **SignerFromPrivateKey** from `@interchainjs/bitcoin/signers/SignerFromPrivateKey` +- **BitcoinAccount** from `@interchainjs/bitcoin/accounts/bitcoin-account` + +## Credits + +🛠 Built by Hyperweb (formerly Cosmology) — if you like our tools, please check out and contribute to [our github ⚛️](https://github.com/hyperweb-io) + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. + diff --git a/networks/bitcoin/package.json b/networks/bitcoin/package.json new file mode 100644 index 000000000..cfef18165 --- /dev/null +++ b/networks/bitcoin/package.json @@ -0,0 +1,33 @@ +{ + "name": "@interchainjs/bitcoin", + "version": "1.11.11", + "description": "Bitcoin signing utilities", + "main": "index.js", + "module": "esm/index.js", + "types": "index.d.ts", + "author": "Hyperweb ", + "homepage": "https://github.com/hyperweb-io/interchainjs", + "repository": { + "type": "git", + "url": "https://github.com/hyperweb-io/interchainjs" + }, + "license": "MIT", + "publishConfig": { + "access": "public", + "directory": "dist" + }, + "scripts": { + "copy": "copyfiles -f ../../LICENSE-MIT ../../LICENSE-Apache README.md package.json dist", + "clean": "rimraf dist/**", + "prepare": "npm run build", + "build": "npm run clean; tsc; tsc -p tsconfig.esm.json; npm run copy", + "build:dev": "npm run clean; tsc --declarationMap; tsc -p tsconfig.esm.json; npm run copy", + "lint": "eslint . --fix" + }, + "dependencies": { + "@interchainjs/auth": "1.11.11", + "@interchainjs/crypto": "1.11.11", + "@interchainjs/types": "1.11.11", + "@interchainjs/utils": "1.11.11" + } +} diff --git a/networks/bitcoin/src/accounts/bitcoin-account.ts b/networks/bitcoin/src/accounts/bitcoin-account.ts new file mode 100644 index 000000000..0be89fa85 --- /dev/null +++ b/networks/bitcoin/src/accounts/bitcoin-account.ts @@ -0,0 +1,14 @@ +import { AccountBase } from '@interchainjs/types/account'; +import { sha256, ripemd160 } from '@interchainjs/crypto'; +import { toBech32 } from '@interchainjs/encoding'; + +export class BitcoinAccount extends AccountBase { + getAddressByPubKey(): string { + const pub = this.auth.getPublicKey(true); + const hash160 = ripemd160(sha256(pub.value)); + const data = new Uint8Array(1 + hash160.length); + data[0] = 0x00; // witness version 0 + data.set(hash160, 1); + return toBech32(this.prefix, data); + } +} diff --git a/networks/bitcoin/src/index.ts b/networks/bitcoin/src/index.ts new file mode 100644 index 000000000..317c058af --- /dev/null +++ b/networks/bitcoin/src/index.ts @@ -0,0 +1,2 @@ +export * from './signers/SignerFromPrivateKey'; +export * from './accounts/bitcoin-account'; diff --git a/networks/bitcoin/src/signers/SignerFromPrivateKey.ts b/networks/bitcoin/src/signers/SignerFromPrivateKey.ts new file mode 100644 index 000000000..bb2a7a620 --- /dev/null +++ b/networks/bitcoin/src/signers/SignerFromPrivateKey.ts @@ -0,0 +1,71 @@ +import { sha256, ripemd160 } from '@interchainjs/crypto'; +import { Key } from '@interchainjs/utils'; +import { secp256k1 } from '@noble/curves/secp256k1'; +import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; + +function base58Encode(data: Uint8Array): string { + const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + const digits = [0]; + for (const byte of data) { + let carry = byte; + for (let i = 0; i < digits.length; i++) { + carry += digits[i] << 8; + digits[i] = carry % 58; + carry = (carry / 58) | 0; + } + while (carry > 0) { + digits.push(carry % 58); + carry = (carry / 58) | 0; + } + } + let zeros = 0; + for (const byte of data) { + if (byte === 0) { + zeros++; + } else { + break; + } + } + let result = ''; + for (let i = 0; i < zeros; i++) { + result += '1'; + } + for (let i = digits.length - 1; i >= 0; i--) { + result += alphabet[digits[i]]; + } + return result; +} + +function base58CheckEncode(data: Uint8Array): string { + const checksum = sha256(sha256(data)).slice(0, 4); + const total = new Uint8Array(data.length + 4); + total.set(data); + total.set(checksum, data.length); + return base58Encode(total); +} + +export class SignerFromPrivateKey { + private privateKey: Uint8Array; + + constructor(privateKeyHex: string) { + this.privateKey = hexToBytes(privateKeyHex.replace(/^0x/, '')); + } + + getPublicKey(compressed = true): Uint8Array { + return secp256k1.getPublicKey(this.privateKey, compressed); + } + + getAddress(prefix = 0x00): string { + const pub = this.getPublicKey(true); + const hash160 = ripemd160(sha256(pub)); + const payload = new Uint8Array(1 + hash160.length); + payload[0] = prefix; + payload.set(hash160, 1); + return base58CheckEncode(payload); + } + + sign(hash: Uint8Array): Uint8Array { + const sig = secp256k1.sign(hash, this.privateKey); + return sig.toCompactRawBytes(); + } +} diff --git a/networks/bitcoin/tsconfig.esm.json b/networks/bitcoin/tsconfig.esm.json new file mode 100644 index 000000000..800d7506d --- /dev/null +++ b/networks/bitcoin/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist/esm", + "module": "es2022", + "rootDir": "src/", + "declaration": false + } +} diff --git a/networks/bitcoin/tsconfig.json b/networks/bitcoin/tsconfig.json new file mode 100644 index 000000000..1a9d5696c --- /dev/null +++ b/networks/bitcoin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src/" + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"] +}