Skip to content

Commit db56c14

Browse files
committed
Migrate to zcash_script
1 parent 997cc34 commit db56c14

File tree

8 files changed

+219
-168
lines changed

8 files changed

+219
-168
lines changed

Cargo.lock

Lines changed: 105 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ tonic = { version = "0.13", features = ["gzip", "tls-webpki-roots"] }
2828
tracing = "0.1"
2929
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
3030
uuid = "1"
31+
zcash_script = "0.2"
3132

3233
orchard = { version = "0.11", default-features = false }
3334
pczt = "0.3"
@@ -105,3 +106,19 @@ tui = [
105106
"dep:tokio-util",
106107
"dep:tui-logger",
107108
]
109+
110+
[patch.crates-io]
111+
zcash_script = { git = "https://github.com/ZcashFoundation/zcash_script.git", rev = "2418a89031fedbe4e5dd2754b12593703490fe76" }
112+
113+
equihash = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
114+
pczt = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
115+
transparent = { package = "zcash_transparent", git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
116+
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
117+
zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
118+
zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
119+
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
120+
zcash_keys = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
121+
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
122+
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
123+
zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }
124+
zip321 = { git = "https://github.com/zcash/librustzcash.git", rev = "29f7c1d9be248c8a548b6c888199f0c660a0d6da" }

src/commands/create_multisig_address.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ use clap::Args;
1313
use secp256k1::PublicKey;
1414
use sha2::{Digest, Sha256};
1515

16-
use ::transparent::address::Script;
1716
use transparent::address::TransparentAddress;
1817
use zcash_keys::encoding::AddressCodec;
1918
use zcash_protocol::consensus::Network;
19+
use zcash_script::{
20+
pattern::check_multisig,
21+
script::{self, Parsable},
22+
};
2023

2124
/// Maximum size of a script element in bytes
2225
// TODO: Move this constant to `zcash_transparent` if it's needed.
@@ -29,7 +32,7 @@ pub(crate) struct Command {
2932
3033
/// A threshold `k` value indicating the number of signatures required to spend from the address
3134
#[clap(short, long, required = true)]
32-
threshold: u32,
35+
threshold: u8,
3336

3437
/// A list of comma-separated hex-encoded public keys.
3538
/// Must contain at least the threshold number of keys.
@@ -53,42 +56,35 @@ impl Command {
5356
let (multisig_script, addr) = multisig_script(threshold, pub_keys)?;
5457
let addr = addr.encode(&Network::from(network));
5558
println!("Created multisig address: {addr}");
56-
println!("Redeem script: {}", hex::encode(multisig_script.0));
59+
println!("Redeem script: {}", hex::encode(multisig_script.to_bytes()));
5760

5861
Ok(())
5962
}
6063
}
6164

6265
fn multisig_script(
63-
threshold: u32,
66+
threshold: u8,
6467
pub_keys: Vec<PublicKey>,
65-
) -> anyhow::Result<(Script, TransparentAddress)> {
68+
) -> anyhow::Result<(script::PubKey, TransparentAddress)> {
6669
validate_args(threshold, &pub_keys)?;
6770

68-
// TODO: Make `Opcode` public and use the enum variants instead of raw opcodes.
69-
let multisig_redeem_script = Script(
70-
std::iter::once(0x50 + threshold as u8) // Push the number of required signatures (OP_1 to OP_16)
71-
.chain(pub_keys.iter().flat_map(|pk| {
72-
let bytes = pk.serialize();
73-
[&[bytes.len() as u8], bytes.as_slice()].concat()
74-
})) // Push each public key
75-
.chain([0x50 + pub_keys.len() as u8, 0xAE]) // Push the number of keys (OP_1 to OP_16) followed by OP_CHECKMULTISIG.
76-
.collect(),
77-
);
71+
let pks = pub_keys.iter().map(|pk| pk.serialize()).collect::<Vec<_>>();
72+
let pks = pks.iter().map(|pk| pk.as_slice()).collect::<Vec<_>>();
73+
let multisig_redeem_script = script::PubKey(check_multisig(threshold, &pks, false));
7874

7975
if multisig_redeem_script.0.len() > MAX_SCRIPT_ELEMENT_SIZE {
8076
return Err(anyhow::anyhow!(
8177
"the multisig script is too large, it must be less than {MAX_SCRIPT_ELEMENT_SIZE} bytes",
8278
));
8379
}
8480

85-
let script_id = ripemd::Ripemd160::digest(Sha256::digest(&multisig_redeem_script.0));
81+
let script_id = ripemd::Ripemd160::digest(Sha256::digest(&multisig_redeem_script.to_bytes()));
8682
let address = TransparentAddress::ScriptHash(script_id.into());
8783

8884
Ok((multisig_redeem_script, address))
8985
}
9086

91-
fn validate_args(threshold: u32, pub_keys: &[PublicKey]) -> anyhow::Result<()> {
87+
fn validate_args(threshold: u8, pub_keys: &[PublicKey]) -> anyhow::Result<()> {
9288
if threshold < 1 {
9389
return Err(anyhow::anyhow!("a multisignature address must require at least one key to redeem, threshold must be at least 1"));
9490
}

src/commands/inspect/context.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ use serde::{
77
Deserialize, Serialize, Serializer,
88
};
99

10-
use ::transparent::{address::Script, bundle as transparent, bundle::TxOut};
10+
use ::transparent::{bundle as transparent, bundle::TxOut};
1111
use zcash_protocol::{
1212
consensus::{Network, NetworkType},
1313
value::Zatoshis,
1414
};
15+
use zcash_script::script::{self, Parsable};
1516
use zip32::AccountId;
1617

1718
#[derive(Clone, Copy, Debug)]
@@ -217,7 +218,7 @@ impl Serialize for ZOutputValue {
217218
}
218219

219220
#[derive(Clone, Debug)]
220-
struct ZScript(Script);
221+
struct ZScript(script::PubKey);
221222

222223
struct ZScriptVisitor;
223224

@@ -243,7 +244,9 @@ impl Visitor<'_> for ZScriptVisitor {
243244
serde::de::Error::invalid_length(v.len(), &"a 64-character string")
244245
}
245246
})?;
246-
Ok(ZScript(Script(data)))
247+
script::PubKey::from_bytes(&data)
248+
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
249+
.map(|(script_pubkey, _)| ZScript(script_pubkey))
247250
}
248251
}
249252

@@ -258,7 +261,7 @@ impl<'de> Deserialize<'de> for ZScript {
258261

259262
impl Serialize for ZScript {
260263
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
261-
serializer.serialize_str(&hex::encode(&self.0 .0))
264+
serializer.serialize_str(&hex::encode(&self.0.to_bytes()))
262265
}
263266
}
264267

src/commands/inspect/transaction.rs

Lines changed: 59 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ use group::GroupEncoding;
88
use sapling::{note_encryption::SaplingDomain, SaplingVerificationContext};
99
use secp256k1::{Secp256k1, VerifyOnly};
1010

11-
#[allow(deprecated)]
12-
use ::transparent::keys::pubkey_to_address;
1311
use ::transparent::{
14-
address::{Script, TransparentAddress},
12+
address::TransparentAddress,
1513
bundle as transparent,
1614
sighash::{SighashType, TransparentAuthorizingContext},
1715
};
@@ -33,6 +31,10 @@ use zcash_protocol::{
3331
memo::{Memo, MemoBytes},
3432
value::Zatoshis,
3533
};
34+
use zcash_script::{
35+
opcode::{Opcode, PushValue, SmallValue},
36+
script,
37+
};
3638

3739
use super::{
3840
context::{Context, ZTxOut},
@@ -46,43 +48,45 @@ pub fn is_coinbase(tx: &Transaction) -> bool {
4648
}
4749

4850
pub fn extract_height_from_coinbase(tx: &Transaction) -> Option<BlockHeight> {
49-
const OP_0: u8 = 0x00;
50-
const OP_1NEGATE: u8 = 0x4f;
51-
const OP_1: u8 = 0x51;
52-
const OP_16: u8 = 0x60;
53-
5451
tx.transparent_bundle()
5552
.and_then(|bundle| bundle.vin.first())
56-
.and_then(|input| match input.script_sig.0.first().copied() {
57-
// {0, -1} will never occur as the first byte of a coinbase scriptSig.
58-
Some(OP_0 | OP_1NEGATE) => None,
59-
// Blocks 1 to 16.
60-
Some(h @ OP_1..=OP_16) => Some(BlockHeight::from_u32((h - OP_1 + 1).into())),
61-
// All other heights use CScriptNum encoding, which will never be longer
62-
// than 5 bytes for Zcash heights. These have the format
63-
// `[len(encoding)] || encoding`.
64-
Some(h @ 1..=5) => {
65-
let rest = &input.script_sig.0[1..];
66-
let encoding_len = h as usize;
67-
if rest.len() < encoding_len {
68-
None
69-
} else {
70-
// Parse the encoding.
71-
let encoding = &rest[..encoding_len];
72-
if encoding.last().unwrap() & 0x80 != 0 {
73-
// Height is never negative.
74-
None
75-
} else {
76-
let mut height: u64 = 0;
77-
for (i, b) in encoding.iter().enumerate() {
78-
height |= (*b as u64) << (8 * i);
53+
.and_then(|input| {
54+
input
55+
.script_sig
56+
.opcodes()
57+
.first()
58+
.and_then(|opcode| match opcode {
59+
Opcode::PushValue(PushValue::SmallValue(v)) => match v {
60+
// // {0, -1} will never occur as the first byte of a coinbase scriptSig.
61+
SmallValue::OP_0 | SmallValue::OP_1NEGATE => None,
62+
// Invalid
63+
SmallValue::OP_RESERVED => None,
64+
// Blocks 1 to 16.
65+
h => Some(BlockHeight::from_u32(u32::from(
66+
*h.value().expect("valid").first().expect("valid"),
67+
))),
68+
},
69+
// All other heights use CScriptNum encoding, which will never be longer
70+
// than 5 bytes for Zcash heights. These have the format
71+
// `[len(encoding)] || encoding`.
72+
// h @ 1..=5 =>
73+
Opcode::PushValue(PushValue::LargeValue(v)) => {
74+
// Parse the encoding.
75+
let encoding = v.value();
76+
if encoding.last().unwrap() & 0x80 != 0 {
77+
// Height is never negative.
78+
None
79+
} else {
80+
let mut height: u64 = 0;
81+
for (i, b) in encoding.iter().enumerate() {
82+
height |= (*b as u64) << (8 * i);
83+
}
84+
height.try_into().ok()
7985
}
80-
height.try_into().ok()
8186
}
82-
}
83-
}
84-
// Anything else is an invalid height encoding.
85-
_ => None,
87+
// Anything else is an invalid height encoding.
88+
_ => None,
89+
})
8690
})
8791
}
8892

@@ -109,7 +113,7 @@ pub(crate) struct TransparentAuth {
109113
}
110114

111115
impl transparent::Authorization for TransparentAuth {
112-
type ScriptSig = Script;
116+
type ScriptSig = script::Sig<Opcode>;
113117
}
114118

115119
impl TransparentAuthorizingContext for TransparentAuth {
@@ -120,7 +124,7 @@ impl TransparentAuthorizingContext for TransparentAuth {
120124
.collect()
121125
}
122126

123-
fn input_scriptpubkeys(&self) -> Vec<Script> {
127+
fn input_scriptpubkeys(&self) -> Vec<script::PubKey> {
124128
self.all_prev_outputs
125129
.iter()
126130
.map(|prevout| prevout.script_pubkey.clone())
@@ -246,39 +250,23 @@ pub(crate) fn inspect(
246250
);
247251
match coin.recipient_address() {
248252
Some(addr @ TransparentAddress::PublicKeyHash(_)) => {
249-
// Format is [sig_and_type_len] || sig || [hash_type] || [pubkey_len] || pubkey
253+
// Format is PushData(sig || [hash_type]) || PushData(pubkey)
250254
// where [x] encodes a single byte.
251-
let sig_and_type_len = txin.script_sig.0.first().map(|l| *l as usize);
252-
let pubkey_len = sig_and_type_len
253-
.and_then(|sig_len| txin.script_sig.0.get(1 + sig_len))
254-
.map(|l| *l as usize);
255-
let script_len = sig_and_type_len.zip(pubkey_len).map(
256-
|(sig_and_type_len, pubkey_len)| {
257-
1 + sig_and_type_len + 1 + pubkey_len
258-
},
259-
);
260-
261-
if Some(txin.script_sig.0.len()) != script_len {
262-
eprintln!(
263-
" ⚠️ \"transparentcoins\" {} is P2PKH; txin {} scriptSig has length {} but data {}",
264-
i,
265-
i,
266-
txin.script_sig.0.len(),
267-
if let Some(l) = script_len {
268-
format!("implies length {l}.")
269-
} else {
270-
"would cause an out-of-bounds read.".to_owned()
271-
},
272-
);
273-
} else {
274-
let sig_len = sig_and_type_len.unwrap() - 1;
275-
276-
let sig = secp256k1::ecdsa::Signature::from_der(
277-
&txin.script_sig.0[1..1 + sig_len],
278-
);
279-
let hash_type = SighashType::parse(txin.script_sig.0[1 + sig_len]);
280-
let pubkey_bytes = &txin.script_sig.0[1 + sig_len + 2..];
281-
let pubkey = secp256k1::PublicKey::from_slice(pubkey_bytes);
255+
let (sig_and_type, pubkey) = match txin.script_sig.opcodes() {
256+
[Opcode::PushValue(sig_and_type), Opcode::PushValue(pubkey)] => {
257+
(sig_and_type.value(), pubkey.value())
258+
}
259+
_ => (None, None),
260+
};
261+
262+
if let Some(((&hash_type, sig), pubkey_bytes)) = sig_and_type
263+
.as_ref()
264+
.and_then(|b| b.split_last())
265+
.zip(pubkey)
266+
{
267+
let sig = secp256k1::ecdsa::Signature::from_der(sig);
268+
let hash_type = SighashType::parse(hash_type);
269+
let pubkey = secp256k1::PublicKey::from_slice(&pubkey_bytes);
282270

283271
if let Err(e) = sig {
284272
eprintln!(
@@ -294,8 +282,7 @@ pub(crate) fn inspect(
294282
if let (Ok(sig), Some(hash_type), Ok(pubkey)) =
295283
(sig, hash_type, pubkey)
296284
{
297-
#[allow(deprecated)]
298-
if pubkey_to_address(&pubkey) != addr {
285+
if TransparentAddress::from_pubkey(&pubkey) != addr {
299286
eprintln!(" ⚠️ Txin {i} pubkey does not match coin's script_pubkey");
300287
}
301288

src/commands/wallet/derive_path.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@ use anyhow::anyhow;
22
use bip32::PublicKey;
33
use clap::Args;
44
use secrecy::ExposeSecret;
5-
use sha2::{Digest, Sha256};
6-
use zcash_address::{ToAddress, ZcashAddress};
7-
use zcash_protocol::{
8-
consensus::{NetworkConstants, Parameters},
9-
PoolType,
10-
};
5+
use transparent::address::TransparentAddress;
6+
use zcash_keys::encoding::AddressCodec;
7+
use zcash_protocol::{consensus::NetworkConstants, PoolType};
118

129
use crate::config::WalletConfig;
1310

@@ -133,11 +130,7 @@ impl Command {
133130
if show_address {
134131
println!(
135132
" - P2PKH address: {}",
136-
ZcashAddress::from_transparent_p2pkh(
137-
params.network_type(),
138-
ripemd::Ripemd160::digest(Sha256::digest(xpub.public_key().to_bytes()))
139-
.into(),
140-
),
133+
TransparentAddress::from_pubkey(xpub.public_key()).encode(&params),
141134
);
142135
}
143136
}

0 commit comments

Comments
 (0)