Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add CachedFfiTransaction::transparent_output_address() #3668

Closed
wants to merge 8 commits into from
7 changes: 4 additions & 3 deletions .github/mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions zebra-chain/src/transparent/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion zebra-script/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
91 changes: 89 additions & 2 deletions zebra-script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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),
Expand All @@ -51,6 +57,9 @@ impl From<zcash_script_error_t> 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),
}
}
Expand Down Expand Up @@ -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<transparent::Address, Error> {
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
Expand Down Expand Up @@ -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::*;

Expand Down Expand Up @@ -327,6 +385,35 @@ mod tests {
Ok(())
}

#[test]
fn get_transparent_output_address() -> Result<()> {
zebra_test::init();

let transaction =
SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;

// 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 <https://zips.z.cash/protocol/protocol.pdf#transparentaddrencoding>
// 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();
Expand Down