feat: get addresses from transparent outputs (#3802)
* feat: add Output::address() * test transactions in test blocks
This commit is contained in:
parent
199267bfa3
commit
9862f6e5cf
|
@ -9,7 +9,7 @@ use std::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
amount::{Amount, NonNegative},
|
amount::{Amount, NonNegative},
|
||||||
parameters::NetworkUpgrade,
|
parameters::{Network, NetworkUpgrade},
|
||||||
serialization::ZcashSerialize,
|
serialization::ZcashSerialize,
|
||||||
transaction::{AuthDigest, HashType, SigHash, Transaction},
|
transaction::{AuthDigest, HashType, SigHash, Transaction},
|
||||||
transparent::{self, Script},
|
transparent::{self, Script},
|
||||||
|
@ -148,3 +148,23 @@ pub(crate) fn auth_digest(trans: &Transaction) -> AuthDigest {
|
||||||
|
|
||||||
AuthDigest(digest_bytes)
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,10 @@ mod tests;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
amount::{Amount, NonNegative},
|
amount::{Amount, NonNegative},
|
||||||
block, transaction,
|
block,
|
||||||
|
parameters::Network,
|
||||||
|
primitives::zcash_primitives,
|
||||||
|
transaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{collections::HashMap, fmt, iter};
|
use std::{collections::HashMap, fmt, iter};
|
||||||
|
@ -303,4 +306,11 @@ impl Output {
|
||||||
pub fn value(&self) -> Amount<NonNegative> {
|
pub fn value(&self) -> Amount<NonNegative> {
|
||||||
self.value
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,6 +192,22 @@ impl ToAddressWithNetwork for PublicKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Address {
|
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
|
/// A hash of a transparent address payload, as used in
|
||||||
/// transparent pay-to-script-hash and pay-to-publickey-hash
|
/// transparent pay-to-script-hash and pay-to-publickey-hash
|
||||||
/// addresses.
|
/// addresses.
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::super::serialize::parse_coinbase_height;
|
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]
|
#[test]
|
||||||
fn parse_coinbase_height_mins() {
|
fn parse_coinbase_height_mins() {
|
||||||
|
@ -37,3 +46,83 @@ fn parse_coinbase_height_mins() {
|
||||||
let case4 = vec![0x04, 0x11, 0x00, 0x00, 0x00];
|
let case4 = vec![0x04, 0x11, 0x00, 0x00, 0x00];
|
||||||
assert!(parse_coinbase_height(case4).is_err());
|
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);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue