Skip to content

Commit 403aebb

Browse files
committed
bring bitcoin changes from core pr
1 parent c4b6583 commit 403aebb

47 files changed

Lines changed: 2700 additions & 105 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/Cargo.lock

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

core/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ chrono = { version = "0.4.43", features = ["serde"] }
9191
# crypto
9292
base64 = { version = "0.22.1" }
9393
bech32 = { version = "0.11.1" }
94-
blake2 = { version = "0.10.6" }
94+
bitcoin = { version = "0.32.7" }
95+
bitcoincash-addr = { version = "0.5.2" }
96+
blake2b_simd = { version = "1.0.4" }
9597
bs58 = { version = "0.5.1", features = ["check"] }
9698
hex = { version = "0.4.3" }
9799
crc = { version = "3.4.0" }

core/crates/gem_bitcoin/Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ publish = false
66

77
[features]
88
default = []
9-
rpc = ["dep:chain_traits", "dep:gem_client"]
10-
signer = ["dep:signer", "dep:gem_hash", "dep:hex"]
9+
rpc = ["dep:chain_traits", "dep:gem_client", "signer"]
10+
signer = ["dep:bech32", "dep:bitcoin", "dep:bitcoincash-addr", "dep:bs58", "dep:gem_hash", "dep:hex", "dep:signer"]
1111
reqwest = ["gem_client/reqwest"]
1212
unit_tests = ["signer"]
1313
chain_integration_tests = ["rpc", "reqwest", "primitives/testkit", "settings/testkit"]
@@ -29,8 +29,14 @@ serde_serializers = { path = "../serde_serializers", features = ["bigint"] }
2929
signer = { path = "../signer", optional = true }
3030
gem_hash = { path = "../gem_hash", optional = true }
3131
hex = { workspace = true, optional = true }
32+
bitcoin = { workspace = true, optional = true }
33+
bitcoincash-addr = { workspace = true, optional = true }
34+
bech32 = { workspace = true, optional = true }
35+
bs58 = { workspace = true, optional = true }
3236

3337
[dev-dependencies]
38+
gem_client = { path = "../gem_client", features = ["testkit"] }
3439
tokio = { workspace = true, features = ["macros", "rt"] }
3540
reqwest = { workspace = true }
41+
primitives = { path = "../primitives", features = ["testkit"] }
3642
settings = { path = "../settings", features = ["testkit"] }
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use bitcoin::ScriptBuf;
2+
use primitives::{Address as AddressTrait, BitcoinChain, Chain};
3+
4+
use crate::signer::address::script_for_address;
5+
6+
#[derive(Debug, Clone)]
7+
pub struct BitcoinAddress {
8+
chain: BitcoinChain,
9+
address: String,
10+
script_pubkey: ScriptBuf,
11+
}
12+
13+
impl BitcoinAddress {
14+
pub fn try_parse_for_chain(address: &str, chain: BitcoinChain) -> Option<Self> {
15+
let script_pubkey = script_for_address(chain, address).ok()?.script_pubkey;
16+
Some(Self {
17+
chain,
18+
address: address.to_string(),
19+
script_pubkey,
20+
})
21+
}
22+
23+
pub fn is_valid_for_chain(address: &str, chain: Chain) -> bool {
24+
BitcoinChain::from_chain(chain).is_some_and(|chain| Self::try_parse_for_chain(address, chain).is_some())
25+
}
26+
27+
pub fn bitcoin_chain(&self) -> BitcoinChain {
28+
self.chain
29+
}
30+
}
31+
32+
impl AddressTrait for BitcoinAddress {
33+
fn try_parse(address: &str) -> Option<Self> {
34+
[
35+
BitcoinChain::Bitcoin,
36+
BitcoinChain::BitcoinCash,
37+
BitcoinChain::Litecoin,
38+
BitcoinChain::Doge,
39+
BitcoinChain::Zcash,
40+
]
41+
.into_iter()
42+
.find_map(|chain| Self::try_parse_for_chain(address, chain))
43+
}
44+
45+
fn as_bytes(&self) -> &[u8] {
46+
self.script_pubkey.as_bytes()
47+
}
48+
49+
fn encode(&self) -> String {
50+
self.address.clone()
51+
}
52+
}
53+
54+
pub fn validate_address(address: &str, chain: Chain) -> bool {
55+
BitcoinAddress::is_valid_for_chain(address, chain)
56+
}
57+
58+
#[cfg(test)]
59+
mod tests {
60+
use super::*;
61+
use primitives::Address as AddressTrait;
62+
63+
#[test]
64+
fn test_validate_address() {
65+
let bitcoin = BitcoinAddress::mock();
66+
let bitcoin_cash = BitcoinAddress::mock_with_chain(BitcoinChain::BitcoinCash);
67+
let litecoin = BitcoinAddress::mock_with_chain(BitcoinChain::Litecoin);
68+
let doge = BitcoinAddress::mock_with_chain(BitcoinChain::Doge);
69+
let zcash = BitcoinAddress::mock_with_chain(BitcoinChain::Zcash);
70+
71+
assert!(validate_address(&bitcoin.encode(), Chain::Bitcoin));
72+
assert!(validate_address(&bitcoin_cash.encode(), Chain::BitcoinCash));
73+
assert!(validate_address(bitcoin_cash.encode().strip_prefix("bitcoincash:").unwrap(), Chain::BitcoinCash));
74+
assert!(validate_address(&litecoin.encode(), Chain::Litecoin));
75+
assert!(validate_address(&doge.encode(), Chain::Doge));
76+
assert!(validate_address(&zcash.encode(), Chain::Zcash));
77+
assert!(!validate_address(&bitcoin.encode(), Chain::Litecoin));
78+
assert!(!validate_address("invalid", Chain::Bitcoin));
79+
80+
let parsed = BitcoinAddress::try_parse_for_chain(&bitcoin.encode(), BitcoinChain::Bitcoin).unwrap();
81+
assert_eq!(parsed.bitcoin_chain().get_chain(), Chain::Bitcoin);
82+
assert_eq!(parsed.encode(), bitcoin.encode());
83+
assert_eq!(hex::encode(parsed.as_bytes()), "0014751e76e8199196d454941c45d1b3a323f1433bd6");
84+
}
85+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use bitcoin::hashes::{Hash, hash160 as bitcoin_hash160, sha256d};
2+
use primitives::SignerError;
3+
4+
pub(crate) const HASH160_LEN: usize = 20;
5+
6+
pub(crate) fn double_sha256(bytes: &[u8]) -> [u8; 32] {
7+
sha256d::Hash::hash(bytes).to_byte_array()
8+
}
9+
10+
pub(crate) fn hash160(bytes: &[u8]) -> [u8; HASH160_LEN] {
11+
bitcoin_hash160::Hash::hash(bytes).to_byte_array()
12+
}
13+
14+
pub(crate) fn public_key_hash(public_key: &[u8]) -> [u8; HASH160_LEN] {
15+
hash160(public_key)
16+
}
17+
18+
pub(crate) fn hash20(bytes: &[u8]) -> Result<[u8; HASH160_LEN], SignerError> {
19+
bytes.try_into().map_err(SignerError::from_display)
20+
}
21+
22+
#[cfg(test)]
23+
mod tests {
24+
use super::*;
25+
26+
#[test]
27+
fn test_hashes() {
28+
assert_eq!(hex::encode(double_sha256(b"")), "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456");
29+
assert_eq!(hex::encode(hash160(b"")), "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb");
30+
assert_eq!(public_key_hash(b""), hash160(b""));
31+
assert_eq!(hash20(&[1u8; HASH160_LEN]).unwrap(), [1u8; HASH160_LEN]);
32+
assert!(hash20(&[1u8; HASH160_LEN - 1]).is_err());
33+
}
34+
}

core/crates/gem_bitcoin/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
pub mod models;
22

3+
#[cfg(feature = "signer")]
4+
pub(crate) mod hash;
5+
6+
#[cfg(feature = "signer")]
7+
pub mod address;
8+
39
#[cfg(feature = "rpc")]
410
pub mod provider;
511

@@ -15,5 +21,8 @@ pub mod testkit;
1521
#[cfg(feature = "rpc")]
1622
pub use provider::map_transaction;
1723

24+
#[cfg(feature = "signer")]
25+
pub use address::{BitcoinAddress, validate_address};
26+
1827
#[cfg(feature = "rpc")]
1928
pub use rpc::client::BitcoinClient;

0 commit comments

Comments
 (0)