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: get addresses from transparent outputs #3802

Merged
merged 2 commits into from
Mar 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion zebra-chain/src/primitives/zcash_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{

use crate::{
amount::{Amount, NonNegative},
parameters::NetworkUpgrade,
parameters::{Network, NetworkUpgrade},
serialization::ZcashSerialize,
transaction::{AuthDigest, HashType, SigHash, Transaction},
transparent::{self, Script},
Expand Down Expand Up @@ -148,3 +148,23 @@ pub(crate) fn auth_digest(trans: &Transaction) -> AuthDigest {

AuthDigest(digest_bytes)
}

/// Return the destination address from a transparent output.
///
/// Returns None if the address type is not valid or unrecognized.
pub(crate) fn transparent_output_address(
output: &transparent::Output,
network: Network,
) -> Option<transparent::Address> {
let script = zcash_primitives::legacy::Script::from(&output.lock_script);
let alt_addr = script.address();
match alt_addr {
Some(zcash_primitives::legacy::TransparentAddress::PublicKey(pub_key_hash)) => Some(
transparent::Address::from_pub_key_hash(network, pub_key_hash),
),
Some(zcash_primitives::legacy::TransparentAddress::Script(script_hash)) => {
Some(transparent::Address::from_script_hash(network, script_hash))
}
None => None,
}
}
12 changes: 11 additions & 1 deletion zebra-chain/src/transparent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ mod tests;

use crate::{
amount::{Amount, NonNegative},
block, transaction,
block,
parameters::Network,
primitives::zcash_primitives,
transaction,
};

use std::{collections::HashMap, fmt, iter};
Expand Down Expand Up @@ -303,4 +306,11 @@ impl Output {
pub fn value(&self) -> Amount<NonNegative> {
self.value
}

/// Return the destination address from a transparent output.
///
/// Returns None if the address type is not valid or unrecognized.
pub fn address(&self, network: Network) -> Option<Address> {
zcash_primitives::transparent_output_address(self, network)
}
}
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
89 changes: 89 additions & 0 deletions zebra-chain/src/transparent/tests/vectors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
use std::sync::Arc;

use super::super::serialize::parse_coinbase_height;
use crate::{
block::Block, parameters::Network, primitives::zcash_primitives::transparent_output_address,
serialization::ZcashDeserializeInto, transaction,
};
use hex::FromHex;

use zebra_test::prelude::*;

#[test]
fn parse_coinbase_height_mins() {
Expand Down Expand Up @@ -37,3 +46,83 @@ fn parse_coinbase_height_mins() {
let case4 = vec![0x04, 0x11, 0x00, 0x00, 0x00];
assert!(parse_coinbase_height(case4).is_err());
}

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

let script_tx: Vec<u8> = <Vec<u8>>::from_hex("0400008085202f8901fcaf44919d4a17f6181a02a7ebe0420be6f7dad1ef86755b81d5a9567456653c010000006a473044022035224ed7276e61affd53315eca059c92876bc2df61d84277cafd7af61d4dbf4002203ed72ea497a9f6b38eb29df08e830d99e32377edb8a574b8a289024f0241d7c40121031f54b095eae066d96b2557c1f99e40e967978a5fd117465dbec0986ca74201a6feffffff020050d6dc0100000017a9141b8a9bda4b62cd0d0582b55455d0778c86f8628f870d03c812030000001976a914e4ff5512ffafe9287992a1cd177ca6e408e0300388ac62070d0095070d000000000000000000000000")
.expect("Block bytes are in valid hex representation");

let transaction = script_tx.zcash_deserialize_into::<Arc<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 addr = transparent_output_address(&transaction.outputs()[0], Network::Mainnet)
.expect("should return address");
assert_eq!(addr.to_string(), "t3M5FDmPfWNRG3HRLddbicsuSCvKuk9hxzZ");
let addr = transparent_output_address(&transaction.outputs()[0], Network::Testnet)
.expect("should return address");
assert_eq!(addr.to_string(), "t294SGSVoNq2daz15ZNbmAW65KQZ5e3nN5G");
// Public key hash e4ff5512ffafe9287992a1cd177ca6e408e03003
let addr = transparent_output_address(&transaction.outputs()[1], Network::Mainnet)
.expect("should return address");
assert_eq!(addr.to_string(), "t1ekRwsd4LaSsd6NXgsx66q2HxQWTLCF44y");
let addr = transparent_output_address(&transaction.outputs()[1], Network::Testnet)
.expect("should return address");
assert_eq!(addr.to_string(), "tmWbBGi7TjExNmLZyMcFpxVh3ZPbGrpbX3H");

Ok(())
}

#[test]
fn get_transparent_output_address_with_blocks() {
zebra_test::init();

get_transparent_output_address_with_blocks_for_network(Network::Mainnet);
get_transparent_output_address_with_blocks_for_network(Network::Testnet);
}

/// Test that the block test vector indexes match the heights in the block data,
/// and that each post-sapling block has a corresponding final sapling root.
fn get_transparent_output_address_with_blocks_for_network(network: Network) {
let block_iter = match network {
Network::Mainnet => zebra_test::vectors::MAINNET_BLOCKS.iter(),
Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(),
};

let mut valid_addresses = 0;

for (&height, block_bytes) in block_iter.skip(1) {
let block = block_bytes
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid");

for (idx, tx) in block.transactions.iter().enumerate() {
for output in tx.outputs() {
let addr = output.address(network);
if addr.is_none() && idx == 0 && output.lock_script.as_raw_bytes()[0] == 0x21 {
// There are a bunch of coinbase transactions with pay-to-pubkey scripts
// which we don't support; skip them
continue;
}
assert!(
addr.is_some(),
"address of {:?}; block #{}; tx #{}; must not be None",
output,
height,
idx,
);
valid_addresses += 1;
}
}
}
// Make sure we didn't accidentally skip all vectors
assert!(valid_addresses > 0);
}