├── rustfmt.toml ├── .gitignore ├── bitcoin-cash-ecc ├── src │ ├── lib.rs │ ├── polyfill.rs │ ├── rust_ecc.rs │ └── c_ecc.rs └── Cargo.toml ├── Cargo.toml ├── bitcoin-cash-base ├── src │ ├── lib.rs │ ├── data_type.rs │ ├── encoding_utils.rs │ └── op.rs └── Cargo.toml ├── bitcoin-cash ├── src │ ├── ecc.rs │ ├── sequence.rs │ ├── serializer.rs │ ├── lib.rs │ ├── tagged_op.rs │ ├── ops.rs │ ├── error.rs │ ├── scripts.rs │ ├── destination.rs │ ├── pubkey.rs │ ├── bitcoin_code.rs │ ├── tx.rs │ ├── tx_preimage.rs │ ├── deserializer.rs │ ├── script.rs │ ├── address.rs │ ├── serialize_json.rs │ └── hash.rs └── Cargo.toml ├── bitcoin-cash-slp ├── Cargo.toml └── src │ └── lib.rs ├── bitcoin-cash-code ├── Cargo.toml ├── tests │ └── test.rs └── src │ └── lib.rs ├── bitcoin-cash-script-macro ├── Cargo.toml ├── src │ ├── lib.rs │ ├── gen_source.rs │ ├── ir.rs │ ├── state.rs │ └── parse.rs └── tests │ └── test.rs └── LICENSE /rustfmt.toml: -------------------------------------------------------------------------------- 1 | wrap_comments = true 2 | comment_width = 100 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .vscode 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /bitcoin-cash-ecc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "c_ecc")] 2 | mod c_ecc; 3 | 4 | #[cfg(feature = "rust_ecc")] 5 | mod rust_ecc; 6 | 7 | mod polyfill; 8 | 9 | pub use polyfill::*; 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "bitcoin-cash", 5 | "bitcoin-cash-base", 6 | "bitcoin-cash-code", 7 | "bitcoin-cash-ecc", 8 | "bitcoin-cash-script-macro", 9 | "bitcoin-cash-slp", 10 | ] 11 | -------------------------------------------------------------------------------- /bitcoin-cash-ecc/src/polyfill.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "rust_ecc")] 2 | pub type SelectedECC = crate::rust_ecc::RustECC; 3 | 4 | #[cfg(feature = "c_ecc")] 5 | pub type SelectedECC = crate::c_ecc::CECC; 6 | 7 | pub fn init_ecc() -> SelectedECC { 8 | SelectedECC::default() 9 | } 10 | -------------------------------------------------------------------------------- /bitcoin-cash-base/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate strum_macros; 3 | #[macro_use] 4 | extern crate thiserror; 5 | 6 | mod byte_array; 7 | mod data_type; 8 | mod integer; 9 | mod op; 10 | mod opcode; 11 | pub mod encoding_utils; 12 | 13 | pub use byte_array::*; 14 | pub use data_type::*; 15 | pub use integer::*; 16 | pub use op::*; 17 | pub use opcode::*; 18 | -------------------------------------------------------------------------------- /bitcoin-cash/src/ecc.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::Result, ByteArray, Pubkey}; 2 | 3 | pub trait ECC: Default { 4 | fn sign(&self, secret_key: &[u8], msg_array: impl Into) -> Result; 5 | 6 | fn verify(&self, pubkey: &[u8], msg_array: &[u8], sig: &[u8]) -> Result; 7 | 8 | fn derive_pubkey(&self, secret_key: &[u8]) -> Result; 9 | 10 | fn normalize_sig(&self, sig: &[u8]) -> Result>; 11 | } 12 | -------------------------------------------------------------------------------- /bitcoin-cash-slp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-cash-slp" 3 | version = "1.0.0-beta.0" 4 | authors = ["tobiasruck "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A library for creating SLP transactions" 8 | homepage = "https://github.com/be-cash/bitcoin-cash" 9 | documentation = "https://github.com/be-cash/bitcoin-cash" 10 | repository = "https://github.com/be-cash/bitcoin-cash" 11 | 12 | [dependencies] 13 | bitcoin-cash = {path="../bitcoin-cash", version="1.0.0-beta.0"} 14 | serde = { version="1.0", features=["derive"] } 15 | -------------------------------------------------------------------------------- /bitcoin-cash/src/sequence.rs: -------------------------------------------------------------------------------- 1 | pub const CSV_TYPE_FLAG: u32 = 1 << 22; 2 | 3 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 4 | pub enum CsvTimedelta { 5 | Blockheight(u16), 6 | Seconds512(u16), 7 | } 8 | 9 | impl CsvTimedelta { 10 | pub fn sequence(self) -> u32 { 11 | match self { 12 | CsvTimedelta::Blockheight(block_height) => { 13 | block_height as u32 14 | } 15 | CsvTimedelta::Seconds512(seconds) => { 16 | (seconds as u32) | CSV_TYPE_FLAG 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bitcoin-cash-code/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-cash-code" 3 | version = "1.0.0-beta.0" 4 | authors = ["tobiasruck "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A procedual macro for serializing Bitcoin Cash structures" 8 | homepage = "https://github.com/be-cash/bitcoin-cash" 9 | documentation = "https://github.com/be-cash/bitcoin-cash" 10 | repository = "https://github.com/be-cash/bitcoin-cash" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = {version="1.0", features=["full"]} 17 | quote = "1.0" 18 | proc-macro2 = "1.0" 19 | 20 | [dev-dependencies] 21 | bitcoin-cash = {path="../bitcoin-cash", version="1.0.0-beta.0"} 22 | -------------------------------------------------------------------------------- /bitcoin-cash/src/serializer.rs: -------------------------------------------------------------------------------- 1 | use crate::{BitcoinCode, ByteArray, error::Result}; 2 | 3 | pub fn encode_bitcoin_code_all<'a, T: 'a>( 4 | values: impl IntoIterator, 5 | ) -> ByteArray 6 | where 7 | T: BitcoinCode, 8 | { 9 | ByteArray::from_parts( 10 | values.into_iter().map(|value| value.ser()), 11 | ) 12 | } 13 | 14 | pub fn decode_bitcoin_code_all(mut byte_array: ByteArray) -> Result> { 15 | let mut items = Vec::new(); 16 | loop { 17 | let (item, rest) = T::deser_rest(byte_array)?; 18 | byte_array = rest; 19 | items.push(item); 20 | if byte_array.is_empty() { 21 | return Ok(items); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bitcoin-cash-ecc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-cash-ecc" 3 | version = "1.0.0-beta.0" 4 | authors = ["tobiasruck "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Elliptic curve cryptography library for the bitcoin-cash library" 8 | homepage = "https://github.com/be-cash/bitcoin-cash" 9 | documentation = "https://github.com/be-cash/bitcoin-cash" 10 | repository = "https://github.com/be-cash/bitcoin-cash" 11 | 12 | [dependencies] 13 | bitcoin-cash = {path="../bitcoin-cash", version="1.0.0-beta.0"} 14 | 15 | [features] 16 | default = ["c_ecc"] 17 | rust_ecc = ["libsecp256k1"] 18 | c_ecc = ["secp256k1"] 19 | 20 | [dependencies.libsecp256k1] 21 | version = "0.3" 22 | optional = true 23 | 24 | [dependencies.secp256k1] 25 | version = "0.19.0" 26 | optional = true 27 | -------------------------------------------------------------------------------- /bitcoin-cash-base/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-cash-base" 3 | version = "1.0.0-beta.0" 4 | authors = ["tobiasruck "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Base library for the bitcoin-cash library" 8 | homepage = "https://github.com/be-cash/bitcoin-cash" 9 | documentation = "https://github.com/be-cash/bitcoin-cash" 10 | repository = "https://github.com/be-cash/bitcoin-cash" 11 | 12 | 13 | [dependencies] 14 | num = "0.3" 15 | num-derive = "0.3" 16 | num-traits = "0.2" 17 | lazy_static = "1.4" 18 | byteorder = "1.3" 19 | hex = "0.4" 20 | serde = {version="1.0", features=["derive"]} 21 | strum = "0.20" 22 | strum_macros = "0.20" 23 | thiserror = "1.0" 24 | 25 | [dev-dependencies] 26 | sha2 = "0.9" 27 | bitcoin-cash = {path="../bitcoin-cash", version="1.0.0-beta.0"} 28 | hex-literal = "0.3" 29 | -------------------------------------------------------------------------------- /bitcoin-cash-code/tests/test.rs: -------------------------------------------------------------------------------- 1 | use bitcoin_cash::{BitcoinCode, ByteArray, Script, Sha256d}; 2 | 3 | #[test] 4 | fn test_tx() { 5 | #[bitcoin_code] 6 | #[derive(BitcoinCode)] 7 | pub struct TxOutpoint { 8 | pub tx_hash: Sha256d, 9 | pub vout: u32, 10 | } 11 | 12 | #[derive(BitcoinCode)] 13 | pub struct TxInput { 14 | pub prev_out: TxOutpoint, 15 | pub script: Script, 16 | pub sequence: u32, 17 | } 18 | 19 | #[derive(BitcoinCode)] 20 | pub struct TxOutput { 21 | pub value: u64, 22 | pub script: Script, 23 | } 24 | 25 | #[derive(BitcoinCode)] 26 | pub struct UnhashedTx { 27 | pub version: i32, 28 | pub inputs: Vec, 29 | pub outputs: Vec, 30 | pub lock_time: u32, 31 | } 32 | 33 | #[derive(BitcoinCode)] 34 | pub struct Tx { 35 | raw: ByteArray, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bitcoin-cash-script-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-cash-script-macro" 3 | version = "1.0.0-beta.0" 4 | authors = ["tobiasruck "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A procedual macro to ease writing bitcoin cash scripts, base library for the bitcoin-cash library" 8 | homepage = "https://github.com/be-cash/bitcoin-cash" 9 | documentation = "https://github.com/be-cash/bitcoin-cash" 10 | repository = "https://github.com/be-cash/bitcoin-cash" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | bitcoin-cash-base = {path="../bitcoin-cash-base", version="1.0.0-beta.0"} 17 | 18 | syn = {version="1.0", features=["full"]} 19 | quote = "1.0" 20 | proc-macro2 = "1.0" 21 | toolchain_find = "0.1" 22 | tempfile = "3.1" 23 | regex = "1.4" 24 | 25 | [dev-dependencies] 26 | bitcoin-cash = {path="../bitcoin-cash", version="1.0.0-beta.0"} 27 | pretty_assertions = "0.6" 28 | hex = "0.4" 29 | -------------------------------------------------------------------------------- /bitcoin-cash/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate thiserror; 3 | 4 | mod address; 5 | mod bitcoin_code; 6 | mod deserializer; 7 | mod destination; 8 | mod ecc; 9 | pub mod error; 10 | mod hash; 11 | mod ops; 12 | mod pubkey; 13 | mod script; 14 | mod scripts; 15 | mod serialize_json; 16 | mod serializer; 17 | mod tagged_op; 18 | mod tx; 19 | mod tx_builder; 20 | mod tx_preimage; 21 | mod sequence; 22 | 23 | pub use address::{Address, AddressType, Prefix}; 24 | pub use bitcoin_code::*; 25 | pub use deserializer::*; 26 | pub use destination::*; 27 | pub use ecc::*; 28 | pub use hash::*; 29 | pub use ops::*; 30 | pub use pubkey::*; 31 | pub use script::*; 32 | pub use scripts::*; 33 | pub use serialize_json::*; 34 | pub use serializer::*; 35 | pub use tagged_op::*; 36 | pub use tx::*; 37 | pub use tx_builder::*; 38 | pub use tx_preimage::*; 39 | pub use sequence::*; 40 | 41 | pub use bitcoin_cash_base::*; 42 | pub use bitcoin_cash_code::*; 43 | pub use bitcoin_cash_script_macro::script; 44 | -------------------------------------------------------------------------------- /bitcoin-cash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-cash" 3 | version = "1.0.0-beta.0" 4 | authors = ["tobiasruck "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A library for creating and parsing Bitcoin Cash trasactions" 8 | homepage = "https://github.com/be-cash/bitcoin-cash" 9 | documentation = "https://github.com/be-cash/bitcoin-cash" 10 | repository = "https://github.com/be-cash/bitcoin-cash" 11 | 12 | [dependencies] 13 | bitcoin-cash-script-macro = {path="../bitcoin-cash-script-macro", version="1.0.0-beta.0"} 14 | bitcoin-cash-code = {path="../bitcoin-cash-code", version="1.0.0-beta.0"} 15 | bitcoin-cash-base = {path="../bitcoin-cash-base", version="1.0.0-beta.0"} 16 | 17 | hex = "0.4" 18 | hex-literal = "0.3" 19 | thiserror = "1.0" 20 | sha-1 = "0.9" 21 | ripemd160 = "0.9" 22 | sha2 = "0.9" 23 | serde = { version="1.0", features=["derive"] } 24 | serde_json = "1.0" 25 | byteorder = "1.3" 26 | bitflags = "1.2" 27 | base64 = "0.13" 28 | bimap = "0.5" 29 | lazy_static = "1.4" 30 | 31 | num = "0.3" 32 | num-derive = "0.3" 33 | num-traits = "0.2" 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /bitcoin-cash/src/tagged_op.rs: -------------------------------------------------------------------------------- 1 | use crate::Op; 2 | use std::borrow::Cow; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct TaggedOp { 6 | pub op: Op, 7 | pub src_file: Cow<'static, str>, 8 | pub src_line: u32, 9 | pub src_column: u32, 10 | pub src_code: Vec<(u32, Cow<'static, str>)>, 11 | pub pushed_names: Option>>>, 12 | pub alt_pushed_names: Option>>>, 13 | } 14 | 15 | impl TaggedOp { 16 | pub fn from_op(op: Op) -> Self { 17 | TaggedOp { 18 | op, 19 | src_file: "".into(), 20 | src_line: 0, 21 | src_column: 0, 22 | src_code: vec![], 23 | pushed_names: None, 24 | alt_pushed_names: None, 25 | } 26 | } 27 | 28 | pub fn named(mut self, name: impl Into>) -> TaggedOp { 29 | self.pushed_names = Some(vec![Some(name.into())]); 30 | self 31 | } 32 | } 33 | 34 | impl PartialEq for TaggedOp { 35 | fn eq(&self, other: &Self) -> bool { 36 | self.op == other.op 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bitcoin-cash/src/ops.rs: -------------------------------------------------------------------------------- 1 | use crate::{Op, Script, TaggedOp}; 2 | use std::borrow::Cow; 3 | 4 | pub trait Ops { 5 | fn ops(&self) -> Cow<[TaggedOp]>; 6 | } 7 | 8 | pub struct TaggedScript { 9 | tagged_ops: Vec, 10 | input_params: std::marker::PhantomData, 11 | } 12 | 13 | impl TaggedScript { 14 | pub fn new(tagged_ops: Vec) -> Self { 15 | TaggedScript { 16 | tagged_ops, 17 | input_params: std::marker::PhantomData, 18 | } 19 | } 20 | 21 | pub fn script_ops(&self) -> impl Iterator { 22 | self.tagged_ops.iter().map(|op| &op.op) 23 | } 24 | 25 | pub fn script(self) -> Script { 26 | Script::new(self.tagged_ops) 27 | } 28 | } 29 | 30 | impl Ops for TaggedScript { 31 | fn ops(&self) -> Cow<[TaggedOp]> { 32 | self.tagged_ops.as_slice().into() 33 | } 34 | } 35 | 36 | impl Clone for TaggedScript { 37 | fn clone(&self) -> Self { 38 | TaggedScript::new(self.tagged_ops.clone()) 39 | } 40 | } 41 | 42 | impl From> for Script { 43 | fn from(script: TaggedScript) -> Self { 44 | Script::new(script.tagged_ops) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bitcoin-cash/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{address::CashAddrError, ByteArrayError, IntegerError, JsonError}; 2 | 3 | #[derive(Error, Clone, Debug, PartialEq)] 4 | pub enum ScriptSerializeError { 5 | #[error("Push too large")] 6 | PushTooLarge, 7 | #[error("Invalid integer")] 8 | InvalidInteger, 9 | } 10 | 11 | impl ScriptSerializeError { 12 | pub fn into_err(self) -> Result { 13 | Err(Error::ScriptSerialize(self)) 14 | } 15 | } 16 | 17 | pub type Result = std::result::Result; 18 | 19 | #[derive(Error, Debug)] 20 | pub enum Error { 21 | #[error("Invalid CashAddr")] 22 | InvalidCashAddr(#[from] CashAddrError), 23 | 24 | #[error("Invalid hex: {0}")] 25 | FromHex(#[from] hex::FromHexError), 26 | 27 | #[error("IO error: {0}")] 28 | Io(#[from] std::io::Error), 29 | 30 | #[error("Utf8 error: {0}")] 31 | Utf8(#[from] std::str::Utf8Error), 32 | 33 | #[error("Serde JSON error: {0}")] 34 | SerdeJson(#[from] serde_json::Error), 35 | 36 | #[error("JSON error: {0}")] 37 | Json(#[from] JsonError), 38 | 39 | #[error("Invalid size: expected {expected}, got {actual}")] 40 | InvalidSize { expected: usize, actual: usize }, 41 | 42 | #[error("Script serialize error: {0}")] 43 | ScriptSerialize(#[from] ScriptSerializeError), 44 | 45 | #[error("Script serialize error: {amount}")] 46 | InsufficientInputAmount { amount: u64 }, 47 | 48 | #[error("Invalid signature format")] 49 | InvalidSignatureFormat, 50 | 51 | #[error("Invalid invalid pubkey")] 52 | InvalidPubkey, 53 | 54 | #[error("Input {input_idx} already spent")] 55 | InputAlreadySigned { input_idx: usize }, 56 | 57 | #[error("Invalid address type")] 58 | InvalidAddressType, 59 | 60 | #[error("Integer error: {0}")] 61 | IntegerError(#[from] IntegerError), 62 | 63 | #[error("Byte array error: {0}")] 64 | ByteArrayError(#[from] ByteArrayError), 65 | 66 | #[error("{0}")] 67 | Msg(String), 68 | } 69 | 70 | impl From for Error { 71 | fn from(msg: String) -> Self { 72 | Error::Msg(msg) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /bitcoin-cash-ecc/src/rust_ecc.rs: -------------------------------------------------------------------------------- 1 | use bitcoin_cash::{ 2 | error::{Error, Result}, 3 | ByteArray, Function, Pubkey, ECC, 4 | }; 5 | use secp256k1::{Message, PublicKey, PublicKeyFormat, SecretKey, Signature}; 6 | 7 | pub struct RustECC; 8 | 9 | impl Default for RustECC { 10 | fn default() -> Self { 11 | RustECC 12 | } 13 | } 14 | 15 | impl ECC for RustECC { 16 | fn sign(&self, secret_key: &[u8], msg_array: impl Into) -> Result { 17 | let msg_array = msg_array.into(); 18 | let sk = SecretKey::parse_slice(secret_key).map_err(|_| Error::InvalidSize { 19 | expected: 32, 20 | actual: secret_key.len(), 21 | })?; 22 | let msg = Message::parse_slice(&msg_array).map_err(|_| Error::InvalidSize { 23 | expected: 32, 24 | actual: msg_array.len(), 25 | })?; 26 | let mut sig = secp256k1::sign(&msg, &sk).0; 27 | sig.normalize_s(); 28 | let sig = sig.serialize_der().as_ref().to_vec(); 29 | Ok(msg_array.apply_function(sig, Function::EcdsaSign)) 30 | } 31 | 32 | fn verify(&self, pubkey: &[u8], msg_array: &[u8], sig_ser: &[u8]) -> Result { 33 | let msg = Message::parse_slice(msg_array).map_err(|_| Error::InvalidSize { 34 | expected: 32, 35 | actual: msg_array.len(), 36 | })?; 37 | let sig = Signature::parse_der(sig_ser).map_err(|_| Error::InvalidSignatureFormat)?; 38 | let pubkey = PublicKey::parse_slice(pubkey, Some(PublicKeyFormat::Compressed)) 39 | .map_err(|_| Error::InvalidPubkey)?; 40 | Ok(secp256k1::verify(&msg, &sig, &pubkey)) 41 | } 42 | 43 | fn derive_pubkey(&self, secret_key: &[u8]) -> Result { 44 | let sk = SecretKey::parse_slice(secret_key).map_err(|_| Error::InvalidSize { 45 | expected: 32, 46 | actual: secret_key.len(), 47 | })?; 48 | Ok(Pubkey::new( 49 | PublicKey::from_secret_key(&sk).serialize_compressed(), 50 | )) 51 | } 52 | 53 | fn normalize_sig(&self, sig_ser: &[u8]) -> Result> { 54 | let mut sig = 55 | Signature::parse_der_lax(sig_ser).map_err(|_| Error::InvalidSignatureFormat)?; 56 | sig.normalize_s(); 57 | Ok(sig.serialize_der().as_ref().to_vec()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /bitcoin-cash-script-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![deny(missing_doc_code_examples)] 3 | 4 | //! Base crate used in the bitcoin-cash crate. 5 | 6 | extern crate proc_macro; 7 | 8 | mod gen_source; 9 | mod generate; 10 | mod ir; 11 | mod parse; 12 | mod state; 13 | 14 | use quote::quote; 15 | 16 | /// Write complex Bitcoin Cash scripts using this macro: 17 | /// ``` 18 | /// use bitcoin_cash::{Opcode::*, Address, ByteArray, Hashed, BitcoinCode}; 19 | /// struct Params { 20 | /// address: Address<'static>, 21 | /// } 22 | /// #[bitcoin_cash::script(P2PKHInputs)] 23 | /// fn p2pkh_script(params: Params, signature: ByteArray, public_key: ByteArray) { 24 | /// OP_DUP(public_key); 25 | /// let pkh = OP_HASH160(public_key); 26 | /// let address = { params.address.hash().as_slice() }; 27 | /// OP_EQUALVERIFY(pkh, address); 28 | /// OP_CHECKSIG(signature, public_key); 29 | /// } 30 | /// let serialized = Params { 31 | /// address: Address::from_cash_addr("bitcoincash:qzt646a0weknq639ck5aq39afcq2n3c0xslfzmdyej") 32 | /// .unwrap() 33 | /// } 34 | /// .p2pkh_script().script().ser(); 35 | /// 36 | /// assert_eq!(hex::encode(serialized), "1976a91497aaebaf766d306a25c5a9d044bd4e00a9c70f3488ac"); 37 | /// ``` 38 | /// 39 | /// This generates a inherent method for the first parameter of the given function 40 | /// which builds a script, and either a struct or an enum for the script inputs. 41 | /// 42 | /// There are two modes of operation, one which generates a struct and one which generates an enum. 43 | #[proc_macro_attribute] 44 | pub fn script( 45 | attr: proc_macro::TokenStream, 46 | item: proc_macro::TokenStream, 47 | ) -> proc_macro::TokenStream { 48 | let attr = syn::parse_macro_input!(attr as syn::AttributeArgs); 49 | let func = syn::parse_macro_input!(item as syn::ItemFn); 50 | let parsed_script = parse::parse_script(attr, func); 51 | let script_ident = quote! {__script_vec}; 52 | let mut generate_script = generate::GenerateScript { 53 | script_ident, 54 | variant_states: state::VariantStates { 55 | states: Default::default(), 56 | predicate_atoms: vec![], 57 | }, 58 | n_ident: 0, 59 | stmt_idx: 0, 60 | max_line_widths: vec![30, 40, 60, 80], 61 | formatted_lines: vec![], 62 | enable_debug: false, 63 | }; 64 | let result = generate_script.run(parsed_script); 65 | result.into() 66 | } 67 | -------------------------------------------------------------------------------- /bitcoin-cash-ecc/src/c_ecc.rs: -------------------------------------------------------------------------------- 1 | use bitcoin_cash::{ 2 | error::{Error, Result}, 3 | ByteArray, Function, Pubkey, ECC, 4 | }; 5 | use secp256k1::{All, Message, PublicKey, Secp256k1, SecretKey, Signature}; 6 | 7 | #[derive(Clone)] 8 | pub struct CECC { 9 | pub curve: Secp256k1, 10 | } 11 | 12 | impl Default for CECC { 13 | fn default() -> Self { 14 | CECC { 15 | curve: Secp256k1::new(), 16 | } 17 | } 18 | } 19 | 20 | impl ECC for CECC { 21 | fn sign(&self, secret_key: &[u8], msg_array: impl Into) -> Result { 22 | let msg_array = msg_array.into(); 23 | let sk = SecretKey::from_slice(secret_key).map_err(|_| Error::InvalidSize { 24 | expected: 32, 25 | actual: secret_key.len(), 26 | })?; 27 | let msg = Message::from_slice(&msg_array).map_err(|_| Error::InvalidSize { 28 | expected: 32, 29 | actual: msg_array.len(), 30 | })?; 31 | let sig = self.curve.sign(&msg, &sk).serialize_der().to_vec(); 32 | Ok(msg_array.apply_function(sig, Function::EcdsaSign)) 33 | } 34 | 35 | fn verify(&self, pubkey: &[u8], msg_array: &[u8], sig_ser: &[u8]) -> Result { 36 | let msg = Message::from_slice(msg_array).map_err(|_| Error::InvalidSize { 37 | expected: 32, 38 | actual: msg_array.len(), 39 | })?; 40 | let sig = Signature::from_der(sig_ser).map_err(|_| Error::InvalidSignatureFormat)?; 41 | let pubkey = PublicKey::from_slice(pubkey).map_err(|_| Error::InvalidPubkey)?; 42 | match self.curve.verify(&msg, &sig, &pubkey) { 43 | Ok(()) => Ok(true), 44 | Err(secp256k1::Error::IncorrectSignature) => Ok(false), 45 | err => { 46 | err.map_err(|_| Error::InvalidSignatureFormat)?; 47 | unreachable!() 48 | } 49 | } 50 | } 51 | 52 | fn derive_pubkey(&self, secret_key: &[u8]) -> Result { 53 | let sk = SecretKey::from_slice(secret_key).map_err(|_| Error::InvalidSize { 54 | expected: 32, 55 | actual: secret_key.len(), 56 | })?; 57 | Ok(Pubkey::new( 58 | PublicKey::from_secret_key(&self.curve, &sk).serialize(), 59 | )) 60 | } 61 | 62 | fn normalize_sig(&self, sig_ser: &[u8]) -> Result> { 63 | let mut sig = 64 | Signature::from_der_lax(sig_ser).map_err(|_| Error::InvalidSignatureFormat)?; 65 | sig.normalize_s(); 66 | Ok(sig.serialize_der().to_vec()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /bitcoin-cash/src/scripts.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{Error, Result}, 3 | Address, AddressType, ByteArray, 4 | Opcode::*, 5 | Pubkey, Script, SigHashFlags, Signatory, SignatoryKindOne, TaggedScript, TxOutput, TxPreimage, 6 | MAX_SIGNATURE_SIZE, 7 | }; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct P2PKHSignatory { 11 | pub pubkey: Pubkey, 12 | pub sig_hash_flags: SigHashFlags, 13 | } 14 | 15 | struct ParamsAddress<'a>(&'a Address<'a>); 16 | 17 | #[crate::script(P2PKHInputs, crate = "crate")] 18 | pub fn p2pkh_script(params: ParamsAddress<'_>, sig: ByteArray, pubkey: ByteArray) { 19 | OP_DUP(pubkey); 20 | let pk_hashed = OP_HASH160(pubkey); 21 | let pk_hash = params.0.hash(); 22 | OP_EQUALVERIFY(pk_hashed, pk_hash); 23 | let success = OP_CHECKSIG(sig, pubkey); 24 | } 25 | 26 | #[crate::script(P2SHInputs, crate = "crate")] 27 | pub fn p2sh_script(params: ParamsAddress<'_>, redeem_script: ByteArray) { 28 | let script_hashed = OP_HASH160(redeem_script); 29 | let script_hash = params.0.hash(); 30 | let success = OP_EQUAL(script_hashed, script_hash); 31 | } 32 | 33 | impl Into