Skip to content

Commit 51fa905

Browse files
feat: add the first version of implementation
1 parent 2243ad5 commit 51fa905

31 files changed

+6536
-0
lines changed

Cargo.lock

Lines changed: 3599 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[package]
2+
name = "ckb-bitcoin-spv-service"
3+
version = "0.1.0"
4+
authors = ["Boyu Yang <[email protected]>"]
5+
edition = "2021"
6+
license = "MIT"
7+
description = "A Bitcoin SPV service which works on CKB."
8+
homepage = "https://github.com/yangby-cryptape/ckb-bitcoin-spv-service"
9+
repository = "https://github.com/yangby-cryptape/ckb-bitcoin-spv-service"
10+
11+
[dependencies]
12+
thiserror = "1.0"
13+
anyhow = "1.0"
14+
log = "0.4"
15+
env_logger = "0.11"
16+
clap = { version = "4.5", features = ["derive"] }
17+
clap-verbosity-flag = "2.2"
18+
faster-hex = "0.9"
19+
zeroize = { version = "1.7", features = ["derive"] }
20+
url = "2.5"
21+
serde = { version = "1.0", features = ["derive"] }
22+
serde_json = "1.0"
23+
24+
reqwest = { version = "0.11", default-features = false, features = ["json", "blocking"] }
25+
jsonrpc-core = "18.0"
26+
jsonrpc-derive = "18.0"
27+
jsonrpc-http-server = "18.0"
28+
jsonrpc-server-utils = "18.0"
29+
tokio = { version = "1.36", features = ["rt-multi-thread", "full"] }
30+
rocksdb = { package = "ckb-rocksdb", version ="=0.21.1", features = ["snappy"], default-features = false }
31+
secp256k1 = "0.24"
32+
33+
bitcoin = { version = "0.31", features = ["serde"] }
34+
ckb-sdk = "3.1"
35+
ckb-types = "0.114"
36+
ckb-jsonrpc-types = "0.114"
37+
ckb-hash = "0.114"
38+
39+
[dependencies.ckb-bitcoin-spv-verifier]
40+
version = "0.1.0"
41+
git = "https://github.com/ckb-cell/ckb-bitcoin-spv"
42+
rev = "2464c8f"
43+
44+
[features]
45+
default = ["default-tls"]
46+
default-tls = ["ckb-sdk/default-tls", "reqwest/default-tls"]
47+
native-tls-vendored = ["ckb-sdk/native-tls-vendored", "reqwest/native-tls-vendored"]
48+
rustls-tls = ["ckb-sdk/rustls-tls", "reqwest/rustls-tls"]
49+
50+
# TODO Remove this after `ckb-sdk>3.1` released.
51+
[patch.crates-io.ckb-sdk]
52+
git = "https://github.com/nervosnetwork/ckb-sdk-rust"
53+
rev = "b792ee7"

Makefile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
CARGO := @cargo
2+
3+
#
4+
# Check
5+
#
6+
7+
check:
8+
${CARGO} check --workspace
9+
10+
fmt:
11+
${CARGO} fmt --all --check
12+
13+
clippy:
14+
${CARGO} clippy --workspace --tests -- --deny warnings
15+
16+
#
17+
# Build
18+
#
19+
20+
doc:
21+
${CARGO} doc --workspace --no-deps
22+
23+
build:
24+
${CARGO} build --workspace
25+
26+
release:
27+
${CARGO} build --workspace --release

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,72 @@ they can be verified on [CKB].
1111

1212
[License]: https://img.shields.io/badge/License-MIT-blue.svg
1313

14+
## Usage
15+
16+
With the command line option `-h`(alias of `--help`), help will be printed.
17+
18+
### JSON-RPC API Reference
19+
20+
- Method `getTxProof`
21+
22+
Arguments:
23+
24+
- `txid` (a hexadecimal string)
25+
26+
A bitcoin transaction id.
27+
28+
**No `0x`-prefix, as same as [the format in Bitcoin RPC APIs](https://developer.bitcoin.org/reference/rpc/gettxoutproof.html#argument-1-txids).**
29+
30+
31+
- `tx-index` (an unsigned integer)
32+
33+
The index of a transaction in the block; starts from 0.
34+
35+
**Service doesn't check it, just pack it into the final proof.**
36+
37+
- `confirmations` (an unsigned integer)
38+
39+
Represents the required acceptance of the transaction by the bitcoin network.
40+
41+
Result:
42+
43+
- `spv_client` ([type: `OutPoint`])
44+
45+
An out point of a SPV client cell in CKB.
46+
47+
- `proof` ([type: `JsonBytes`])
48+
49+
The full proof of a bitcoin transaction, which could be verified with
50+
above SPV client in CKB.
51+
52+
<details><summary>An example:</summary>
53+
54+
55+
Request:
56+
57+
```shell
58+
curl -X POST -H "Content-Type: application/json" \
59+
-d '{"jsonrpc": "2.0", "method":"getTxProof", "params": ["95231964950ce016b1333ecc1ec98bf4effc3d5d579c5c7232dddd7c2200f124", 9, 10], "id": 1}' \
60+
http://127.0.0.1:8888
61+
```
62+
63+
Output:
64+
```json
65+
{
66+
"jsonrpc": "2.0",
67+
"result": {
68+
"proof": "0x3d03000014000000180000001c000000f90100000900000040bc0c00d9010000000018228d263ff4070e2fdb31b654704e99f850a4f31762155003000000000000000000f79ccd11440a9d383b1438ee60692fc7b78a4b8800faf6ec61ec5fc37a078387f998f265595a03175799484bca0700000c99f30ab6b29578c47b5e290383c73016dc51ceb8d8e4e4772b8914074d261cea95fae35702bd7842c06314b56f2fccf4f1e7a7a1e9316510eac47b8da580bd8c24f100227cdddd32725c9c575d3dfceff48bc91ecc3e33b116e00c95641923959c4eadf6c5194ce92e4af499234915ae16ef6ce925319ecdb782c15b7b79b8e0add7f560c4894d1a6373d3a0f0b2b7809de8c68881212e5804c973d810fac6ab81b4d8171567ed7b4c562cfd92f340f372d4d783ddaec72f4ae0898fa4f1a1609462a8aaa2c0038416074bef53effa98f61229bf5fba767cead3344362b1d9b440b1367c8597e5f42c52fb8b861ff7dd06ce2b6f8a1e7cb41197909a476e625c11f26490c00f5c00aa247fbe7b82cb4d42255596005d6aea0c313c425044da0155c1e3eb8cc65279a99de185af0c57f18f41ead7d93ba6dc6613f2ad338a0bc73d7c8c15eabf47c5de917babf44ce66982367ff38d9a586589fa525b72e6d8c7814386fdb76da0db6f786b020a558e7966dc94e5f75d9de94784297048c9c80103ff2e000800000041bc0c0041bc0c0083ccd2727719f195c487e165c7cebebabe280f08c4060100000000000000000042bc0c0043bc0c001e1ae1b8a976a99d3000d708b5cf41bd34a3610e18d33c7ea12090182c84fa7544bc0c0047bc0c0043bd45f3f7425544324bbdd6303adb92fe446555b5ec5548f4e481bdced123e748bc0c004fbc0c00bd692cd46c3fec36a58f2a963e8b88fb183b46848e2743dd3e599486c466bd7450bc0c005fbc0c000e8775e71c17af3afd9129e0b9cf76d1af0cc7cd49ef5e98d0c25a72b66349ac60bc0c007fbc0c001f8d8f32d7fd85ee07f5e5e9c280bab9ae9fe148bf2c03e45e786fc23024116280bc0c00bfbc0c0016b40a5455bd596f89b58207e272a8f333c03f1ec7e9dd9a1a47c20d4371eb63c0bc0c00c8bc0c006d3b9e55c56dd1fb5777bc115db1faca7a7f6e33cd9823f3a223581c740f833b",
69+
"spv_client": {
70+
"index": "0x1",
71+
"tx_hash": "0xcaee4eac91a43642464ee2c6582a86835f2bc10d55b15fcd22083ae9d273a1dc"
72+
}
73+
},
74+
"id": 1
75+
}
76+
```
77+
78+
</details>
79+
1480
## Related Projects
1581

1682
- [The Core Library of CKB Bitcoin SPV][Bitcoin SPV on CKB]
@@ -25,4 +91,7 @@ Licensed under [MIT License].
2591
[CKB]: https://github.com/nervosnetwork/ckb
2692
[Bitcoin SPV on CKB]: https://github.com/ckb-cell/ckb-bitcoin-spv
2793

94+
[type: `OutPoint`]: https://github.com/nervosnetwork/ckb/tree/v0.114.0/rpc#type-outpoint
95+
[type: `JsonBytes`]: https://github.com/nervosnetwork/ckb/tree/v0.114.0/rpc#type-jsonbytes
96+
2897
[MIT License]: LICENSE

src/cli/deploy.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//! The `deploy` sub-command.
2+
3+
use ckb_jsonrpc_types::TransactionView;
4+
use ckb_sdk::{
5+
transaction::{
6+
builder::{CkbTransactionBuilder, SimpleTransactionBuilder},
7+
input::InputIterator,
8+
signer::{SignContexts, TransactionSigner},
9+
TransactionBuilderConfiguration,
10+
},
11+
types::{
12+
Address as CkbAddress, AddressPayload as CkbAddressPayload, HumanCapacity, NetworkInfo,
13+
},
14+
SECP256K1,
15+
};
16+
use ckb_types::{bytes::Bytes, core::Capacity, packed, prelude::*};
17+
use clap::Parser;
18+
use secp256k1::SecretKey;
19+
20+
use crate::{
21+
prelude::*,
22+
result::{Error, Result},
23+
utilities::value_parsers,
24+
};
25+
26+
#[derive(Parser)]
27+
pub struct Args {
28+
#[clap(flatten)]
29+
pub(crate) common: super::CommonArgs,
30+
31+
#[clap(flatten)]
32+
pub(crate) ckb: super::CkbArgs,
33+
34+
/// A binary file, which should contain the Bitcoin SPV contract.
35+
///
36+
/// The repository of the contract source code is
37+
/// <https://github.com/ckb-cell/ckb-bitcoin-spv-contracts>.
38+
///
39+
/// ### Warnings
40+
///
41+
/// Under the development phase, the compatibility has chance to be broken
42+
/// without any declaration.
43+
///
44+
/// Please always use the latest versions of both the service and the contract.
45+
///
46+
/// TODO Matched versions of the contracts should be list.
47+
#[arg(
48+
long = "contract-file", value_name = "CONTRACT_FILE", required = true,
49+
value_parser = value_parsers::BinaryFileValueParser
50+
)]
51+
pub(crate) contract_data: Bytes,
52+
53+
/// The contract owner's address.
54+
#[arg(long="contract-owner", value_parser = value_parsers::AddressValueParser)]
55+
pub(crate) contract_owner: CkbAddress,
56+
57+
/// Perform all steps without sending.
58+
#[arg(long, hide = true)]
59+
pub(crate) dry_run: bool,
60+
}
61+
62+
impl Args {
63+
// TODO Deploy the Bitcoin SPV contract as type script.
64+
pub fn execute(&self) -> Result<()> {
65+
log::info!("Try to deploy a contract on CKB.");
66+
67+
if self.contract_owner.network() != self.ckb.network {
68+
let msg = "The input addresses and the selected network are not matched.";
69+
return Err(Error::Cli(msg.to_owned()));
70+
}
71+
72+
let contract_data_capacity = Capacity::bytes(self.contract_data.len()).map_err(|err| {
73+
let msg = format!("failed to calculate the capacity for contract data since {err}");
74+
Error::other(msg)
75+
})?;
76+
log::info!(
77+
"The contract requires {} CKBytes for its data.",
78+
HumanCapacity::from(contract_data_capacity.as_u64())
79+
);
80+
81+
let network_info =
82+
NetworkInfo::new(self.ckb.network, self.ckb.ckb_endpoint.as_str().to_owned());
83+
let configuration = {
84+
let mut tmp = TransactionBuilderConfiguration::new_with_network(network_info.clone())?;
85+
tmp.fee_rate = self.ckb.fee_rate;
86+
tmp
87+
};
88+
89+
let output = packed::CellOutput::new_builder()
90+
.lock((&self.contract_owner).into())
91+
.build_exact_capacity(contract_data_capacity)
92+
.map_err(|err| {
93+
let msg = format!("failed to calculate the capacity for the output since {err}");
94+
Error::other(msg)
95+
})?;
96+
97+
let (deployer, deployer_key) = SecretKey::from_slice(&self.common.private_key.as_ref()[..])
98+
.map(|sk| {
99+
let pk = sk.public_key(&SECP256K1);
100+
let payload = CkbAddressPayload::from_pubkey(&pk);
101+
let address = CkbAddress::new(self.ckb.network, payload, true);
102+
(address, sk)
103+
})?;
104+
log::info!("The contract deployer is {deployer}.");
105+
106+
let iterator = InputIterator::new_with_address(&[deployer], &network_info);
107+
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);
108+
builder.add_output_and_data(output, self.contract_data.pack());
109+
let data_hash = packed::CellOutput::calc_data_hash(&self.contract_data);
110+
log::info!("The contract data hash is {data_hash:#x}.");
111+
112+
let mut tx_with_groups = builder.build(&Default::default())?;
113+
114+
TransactionSigner::new(&network_info).sign_transaction(
115+
&mut tx_with_groups,
116+
&SignContexts::new_sighash(vec![deployer_key]),
117+
)?;
118+
119+
let tx_json = TransactionView::from(tx_with_groups.get_tx_view().clone());
120+
self.ckb
121+
.client()
122+
.send_transaction_ext(tx_json, self.dry_run)?;
123+
124+
Ok(())
125+
}
126+
}

0 commit comments

Comments
 (0)