Skip to content

Commit

Permalink
Implement Base64 encoding for ZIP 304 signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Nov 13, 2023
1 parent e498e89 commit 4d67c1d
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions zcash_primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ hdwallet = { workspace = true, optional = true }
# - `SecretKey` and `PublicKey` types exposed
secp256k1 = { workspace = true, optional = true }

# - ZIP 304
base64.workspace = true

# - ZIP 339
bip0039 = { version = "0.10", features = ["std", "all-languages"] }

Expand Down
113 changes: 111 additions & 2 deletions zcash_primitives/src/sapling/zip304.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
//!
//! [ZIP 304]: https://zips.z.cash/zip-0304
use std::{convert::TryInto, error, fmt, str::FromStr};

use base64::prelude::{Engine, BASE64_STANDARD};
use bellman::{
gadgets::multipack,
groth16::{verify_proof, Proof},
};
use group::{ff::Field, Curve};
use rand_core::OsRng;
use std::convert::TryInto;

use super::{
bundle::GrothProofBytes,
Expand Down Expand Up @@ -97,6 +99,75 @@ impl Signature {
}
}

/// Errors that can occur when parsing a ZIP 304 signature from a string.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
Base64(base64::DecodeSliceError),
InvalidLength,
InvalidPrefix,
InvalidSignatureData,
}

impl From<base64::DecodeSliceError> for ParseError {
fn from(e: base64::DecodeSliceError) -> Self {
ParseError::Base64(e)
}
}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Base64(e) => write!(f, "Invalid Base64: {}", e),
ParseError::InvalidLength => {
write!(
f,
"Signature length is invalid (should be 435 characters including prefix)"
)
}
ParseError::InvalidPrefix => write!(f, "Invalid prefix (should be 'zip304:')"),
ParseError::InvalidSignatureData => {
write!(f, "Base64 payload does not encode a ZIP 304 signature")
}
}
}
}

impl error::Error for ParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
ParseError::Base64(e) => Some(e),
_ => None,
}
}
}

impl FromStr for Signature {
type Err = ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_at(7) {
("zip304:", encoded) => {
if encoded.len() == 428 {
// We need an extra byte to decode into.
let mut bytes = [0; 321];
BASE64_STANDARD.decode_slice(encoded, &mut bytes)?;
Signature::from_bytes(&bytes[..320].try_into().unwrap())
.ok_or(ParseError::InvalidSignatureData)
} else {
Err(ParseError::InvalidLength)
}
}
_ => Err(ParseError::InvalidPrefix),
}
}
}

impl ToString for Signature {
fn to_string(&self) -> String {
format!("zip304:{}", BASE64_STANDARD.encode(self.to_bytes()))
}
}

/// Signs an arbitrary message for the given [`PaymentAddress`] and [`SLIP 44`] coin type.
///
/// The coin type is used here in its index form, not its hardened form (i.e. 133 for
Expand Down Expand Up @@ -255,7 +326,7 @@ mod tests {
use crate::sapling::{
circuit::SpendParameters,
keys::ExpandedSpendingKey,
zip304::{sign_message, verify_message},
zip304::{sign_message, verify_message, Signature},
Diversifier,
};

Expand Down Expand Up @@ -312,4 +383,42 @@ mod tests {
assert_ne!(&sig1.to_bytes()[..], &sig1_b.to_bytes()[..]);
assert_ne!(sig1.nullifier, sig1_b.nullifier);
}

#[test]
fn encoding_round_trip() {
let (spend_buf, _) = wagyu_zcash_parameters::load_sapling_parameters();
let params = SpendParameters::read(&spend_buf[..], false)
.expect("Sapling parameters should be valid");

let expsk = ExpandedSpendingKey::from_spending_key(&[42; 32][..]);
let addr = {
let diversifier = Diversifier([0; 11]);
expsk
.proof_generation_key()
.to_viewing_key()
.to_payment_address(diversifier)
.unwrap()
};

let msg = "Foo bar";
let sig = sign_message(&expsk, addr.clone(), 1, msg, &params);

let sigs_equal = |a: Signature, b: &Signature| {
let mut a_sig_bytes = vec![];
let mut b_sig_bytes = vec![];
a.spend_auth_sig.write(&mut a_sig_bytes).unwrap();
b.spend_auth_sig.write(&mut b_sig_bytes).unwrap();
a.nullifier == b.nullifier
&& a.rk.0 == b.rk.0
&& a.zkproof == b.zkproof
&& a_sig_bytes == b_sig_bytes
};

assert!(sigs_equal(
Signature::from_bytes(&sig.to_bytes()).unwrap(),
&sig
));

assert!(sigs_equal(sig.to_string().parse().unwrap(), &sig));
}
}

0 comments on commit 4d67c1d

Please sign in to comment.