diff --git a/.github/mergify.yml b/.github/mergify.yml index c9a991c166f..3dd25479f78 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -11,7 +11,8 @@ queue_rules: - check-success=Test stable zebra-state with fake activation heights on ubuntu-latest - check-success=Test stable on ubuntu-latest - check-success=Test stable on macOS-latest - - check-success=Test stable on windows-latest + # Windows was removed for now, see https://github.com/ZcashFoundation/zebra/discussions/3704 + # - check-success=Test stable on windows-latest - check-success=Coverage nightly - check-success=Clippy - check-success=Rustfmt @@ -28,7 +29,7 @@ queue_rules: - check-success=Test stable zebra-state with fake activation heights on ubuntu-latest - check-success=Test stable on ubuntu-latest - check-success=Test stable on macOS-latest - - check-success=Test stable on windows-latest + # - check-success=Test stable on windows-latest - check-success=Coverage nightly - check-success=Clippy - check-success=Rustfmt @@ -45,7 +46,7 @@ queue_rules: - check-success=Test stable zebra-state with fake activation heights on ubuntu-latest - check-success=Test stable on ubuntu-latest - check-success=Test stable on macOS-latest - - check-success=Test stable on windows-latest + # - check-success=Test stable on windows-latest - check-success=Coverage nightly - check-success=Clippy - check-success=Rustfmt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5d0845fc2b..29b504d673e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + # Windows was removed for now, see https://github.com/ZcashFoundation/zebra/discussions/3704 + os: [ubuntu-latest, macOS-latest] rust: [stable] steps: diff --git a/Cargo.lock b/Cargo.lock index c8629f3ff31..8235549e2e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5624,7 +5624,7 @@ dependencies = [ [[package]] name = "zcash_script" version = "0.1.6-alpha.0" -source = "git+https://github.com/ZcashFoundation/zcash_script.git?rev=270d32d192c5880f911acf21ef100caa128e6179#270d32d192c5880f911acf21ef100caa128e6179" +source = "git+https://github.com/ZcashFoundation/zcash_script.git?rev=b7801e4027f594abfe58aaf1c3b6085caacfaf4f#b7801e4027f594abfe58aaf1c3b6085caacfaf4f" dependencies = [ "bindgen", "blake2b_simd", diff --git a/zebra-chain/src/transparent/address.rs b/zebra-chain/src/transparent/address.rs index 7cc31ee7841..e4a2ad1bb1e 100644 --- a/zebra-chain/src/transparent/address.rs +++ b/zebra-chain/src/transparent/address.rs @@ -192,6 +192,22 @@ impl ToAddressWithNetwork for PublicKey { } impl Address { + /// Create an address for the given public key hash and network. + pub fn from_pub_key_hash(network: Network, pub_key_hash: [u8; 20]) -> Self { + Self::PayToPublicKeyHash { + network, + pub_key_hash, + } + } + + /// Create an address for the given script hash and network. + pub fn from_script_hash(network: Network, script_hash: [u8; 20]) -> Self { + Self::PayToScriptHash { + network, + script_hash, + } + } + /// A hash of a transparent address payload, as used in /// transparent pay-to-script-hash and pay-to-publickey-hash /// addresses. diff --git a/zebra-script/Cargo.toml b/zebra-script/Cargo.toml index 6cc80a8ca80..f8a3f10f6c0 100644 --- a/zebra-script/Cargo.toml +++ b/zebra-script/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -zcash_script = { git = "https://github.com/ZcashFoundation/zcash_script.git", rev = "270d32d192c5880f911acf21ef100caa128e6179" } +zcash_script = { git = "https://github.com/ZcashFoundation/zcash_script.git", rev = "b7801e4027f594abfe58aaf1c3b6085caacfaf4f" } zebra-chain = { path = "../zebra-chain" } diff --git a/zebra-script/src/lib.rs b/zebra-script/src/lib.rs index 14566281fc3..b7045e21486 100644 --- a/zebra-script/src/lib.rs +++ b/zebra-script/src/lib.rs @@ -14,11 +14,14 @@ use zcash_script::{ zcash_script_error_t, zcash_script_error_t_zcash_script_ERR_OK, zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE, zcash_script_error_t_zcash_script_ERR_TX_INDEX, + zcash_script_error_t_zcash_script_ERR_TX_INVALID_SCRIPT, zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH, }; use zebra_chain::{ - parameters::ConsensusBranchId, serialization::ZcashSerialize, transaction::Transaction, + parameters::{ConsensusBranchId, Network}, + serialization::ZcashSerialize, + transaction::Transaction, transparent, }; @@ -38,6 +41,9 @@ pub enum Error { /// tx is an invalid size for it's protocol #[non_exhaustive] TxSizeMismatch, + /// output script type was not recognized + #[non_exhaustive] + TxInvalidOutputScriptType, /// encountered unknown error kind from zcash_script: {0} #[non_exhaustive] Unknown(zcash_script_error_t), @@ -51,6 +57,9 @@ impl From for Error { zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE => Error::TxDeserialize, zcash_script_error_t_zcash_script_ERR_TX_INDEX => Error::TxIndex, zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH => Error::TxSizeMismatch, + zcash_script_error_t_zcash_script_ERR_TX_INVALID_SCRIPT => { + Error::TxInvalidOutputScriptType + } unknown => Error::Unknown(unknown), } } @@ -226,6 +235,53 @@ impl CachedFfiTransaction { Err(Error::from(err)) } } + + /// Returns the destination address of the transparent output at `output_index`. + pub fn transparent_output_address( + &self, + network: Network, + output_index: usize, + ) -> Result { + let mut err = 0; + let mut addr_type = 0; + + // This conversion is useful on some platforms, but not others. + #[allow(clippy::useless_conversion)] + let n_out = output_index + .try_into() + .expect("transaction indexes are much less than c_uint::MAX"); + + // SAFETY: + // `CachedFfiTransaction::new` makes sure `self.precomputed` is not NULL. + // `n_out` is checked by the called function. + // All other arguments are local stack references, which are always valid. + let address_hash = unsafe { + zcash_script::zcash_script_transparent_output_address_precomputed( + self.precomputed, + n_out, + &mut addr_type, + &mut err, + ) + }; + + if err == zcash_script_error_t_zcash_script_ERR_OK { + if addr_type == zcash_script::zcash_script_type_t_zcash_script_TYPE_P2PKH { + Ok(transparent::Address::from_pub_key_hash( + network, + address_hash.value, + )) + } else if addr_type == zcash_script::zcash_script_type_t_zcash_script_TYPE_P2SH { + Ok(transparent::Address::from_script_hash( + network, + address_hash.value, + )) + } else { + unreachable!("only P2PKH and P2SH are recognized by zcash_script"); + } + } else { + Err(Error::from(err)) + } + } } // # SAFETY @@ -279,7 +335,9 @@ mod tests { use std::convert::TryInto; use std::sync::Arc; use zebra_chain::{ - parameters::NetworkUpgrade::*, serialization::ZcashDeserializeInto, transparent, + parameters::{Network, NetworkUpgrade::*}, + serialization::ZcashDeserializeInto, + transparent, }; use zebra_test::prelude::*; @@ -327,6 +385,35 @@ mod tests { Ok(()) } + #[test] + fn get_transparent_output_address() -> Result<()> { + zebra_test::init(); + + let transaction = + SCRIPT_TX.zcash_deserialize_into::>()?; + + // Hashes were extracted from the transaction (parsed with zebra-chain, + // then manually extracted from lock_script). + // Final expected values were generated with https://secretscan.org/PrivateKeyHex, + // by filling field 4 with the prefix followed by the address hash. + // Refer to + // for the prefixes. + + // Script hash 1b8a9bda4b62cd0d0582b55455d0778c86f8628f + let cached_tx = super::CachedFfiTransaction::new(transaction, vec![]); + let addr = cached_tx.transparent_output_address(Network::Mainnet, 0)?; + assert_eq!(addr.to_string(), "t3M5FDmPfWNRG3HRLddbicsuSCvKuk9hxzZ"); + let addr = cached_tx.transparent_output_address(Network::Testnet, 0)?; + assert_eq!(addr.to_string(), "t294SGSVoNq2daz15ZNbmAW65KQZ5e3nN5G"); + // Public key hash e4ff5512ffafe9287992a1cd177ca6e408e03003 + let addr = cached_tx.transparent_output_address(Network::Mainnet, 1)?; + assert_eq!(addr.to_string(), "t1ekRwsd4LaSsd6NXgsx66q2HxQWTLCF44y"); + let addr = cached_tx.transparent_output_address(Network::Testnet, 1)?; + assert_eq!(addr.to_string(), "tmWbBGi7TjExNmLZyMcFpxVh3ZPbGrpbX3H"); + + Ok(()) + } + #[test] fn fail_invalid_script() -> Result<()> { zebra_test::init();