//! Sapling key derivation according to ZIP 32 and ZIP 316
//!
//! Implements [section 4.2.2] of the Zcash Protocol Specification.
//!
//! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents

use aes::Aes256;
use blake2b_simd::Params as Blake2bParams;
use fpe::ff1::{BinaryNumeralString, FF1};
use subtle::CtOption;
use zcash_spec::PrfExpand;
use zip32::{ChainCode, ChildIndex, DiversifierIndex, Scope};

use core::ops::AddAssign;
use corez::io::{self, Read, Write};

use super::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey};
use crate::note_encryption::PreparedIncomingViewingKey;
use crate::{
    constants::PROOF_GENERATION_KEY_GENERATOR,
    keys::{
        DecodingError, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey,
    },
    SaplingIvk,
};

pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Sapling";
pub const ZIP32_SAPLING_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashSaplingFVFP";
pub const ZIP32_SAPLING_INT_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingInt";

/// Attempt to produce a payment address given the specified diversifier
/// index, and return None if the specified index does not produce a valid
/// diversifier.
pub fn sapling_address(
    fvk: &FullViewingKey,
    dk: &DiversifierKey,
    j: DiversifierIndex,
) -> Option<PaymentAddress> {
    dk.diversifier(j)
        .and_then(|d_j| fvk.vk.to_payment_address(d_j))
}

/// Search the diversifier space starting at diversifier index `j` for
/// one which will produce a valid diversifier, and return the payment address
/// constructed using that diversifier along with the index at which the
/// valid diversifier was found.
pub fn sapling_find_address(
    fvk: &FullViewingKey,
    dk: &DiversifierKey,
    j: DiversifierIndex,
) -> Option<(DiversifierIndex, PaymentAddress)> {
    let (j, d_j) = dk.find_diversifier(j)?;
    fvk.vk.to_payment_address(d_j).map(|addr| (j, addr))
}

/// Returns the payment address corresponding to the smallest valid diversifier
/// index, along with that index.
pub fn sapling_default_address(
    fvk: &FullViewingKey,
    dk: &DiversifierKey,
) -> (DiversifierIndex, PaymentAddress) {
    // This unwrap is safe, if you have to search the 2^88 space of
    // diversifiers it'll never return anyway.
    sapling_find_address(fvk, dk, DiversifierIndex::new()).unwrap()
}

/// Convenience function for child OVK derivation
fn derive_child_ovk(parent: &OutgoingViewingKey, i_l: &[u8]) -> OutgoingViewingKey {
    let mut ovk = [0u8; 32];
    ovk.copy_from_slice(&PrfExpand::SAPLING_ZIP32_CHILD_OVK.with(i_l, &parent.0)[..32]);
    OutgoingViewingKey(ovk)
}

/// Returns the internal full viewing key and diversifier key
/// for the provided external FVK = (ak, nk, ovk) and dk encoded
/// in a [Unified FVK].
///
/// [Unified FVK]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys
pub fn sapling_derive_internal_fvk(
    fvk: &FullViewingKey,
    dk: &DiversifierKey,
) -> (FullViewingKey, DiversifierKey) {
    let i = {
        let mut h = Blake2bParams::new()
            .hash_length(32)
            .personal(ZIP32_SAPLING_INT_PERSONALIZATION)
            .to_state();
        h.update(&fvk.to_bytes());
        h.update(&dk.0);
        h.finalize()
    };
    let i_nsk =
        jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_ZIP32_INTERNAL_NSK.with(i.as_bytes()));
    let r = PrfExpand::SAPLING_ZIP32_INTERNAL_DK_OVK.with(i.as_bytes());
    // PROOF_GENERATION_KEY_GENERATOR = \mathcal{H}^Sapling
    let nk_internal = NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * i_nsk + fvk.vk.nk.0);
    let dk_internal = DiversifierKey(r[..32].try_into().unwrap());
    let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap());

    (
        FullViewingKey {
            vk: ViewingKey {
                ak: fvk.vk.ak.clone(),
                nk: nk_internal,
            },
            ovk: ovk_internal,
        },
        dk_internal,
    )
}

/// A Sapling full viewing key fingerprint
struct FvkFingerprint([u8; 32]);

impl From<&FullViewingKey> for FvkFingerprint {
    fn from(fvk: &FullViewingKey) -> Self {
        let mut h = Blake2bParams::new()
            .hash_length(32)
            .personal(ZIP32_SAPLING_FVFP_PERSONALIZATION)
            .to_state();
        h.update(&fvk.to_bytes());
        let mut fvfp = [0u8; 32];
        fvfp.copy_from_slice(h.finalize().as_bytes());
        FvkFingerprint(fvfp)
    }
}

impl FvkFingerprint {
    fn tag(&self) -> FvkTag {
        let mut tag = [0u8; 4];
        tag.copy_from_slice(&self.0[..4]);
        FvkTag(tag)
    }
}

/// A Sapling full viewing key tag
#[derive(Clone, Copy, Debug, PartialEq)]
struct FvkTag([u8; 4]);

impl FvkTag {
    fn master() -> Self {
        FvkTag([0u8; 4])
    }

    fn as_bytes(&self) -> &[u8; 4] {
        &self.0
    }
}

/// A key used to derive diversifiers for a particular child key
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiversifierKey([u8; 32]);

impl DiversifierKey {
    pub fn master(sk_m: &[u8]) -> Self {
        let mut dk_m = [0u8; 32];
        dk_m.copy_from_slice(&PrfExpand::SAPLING_ZIP32_MASTER_DK.with(sk_m)[..32]);
        DiversifierKey(dk_m)
    }

    /// Constructs the diversifier key from its constituent bytes.
    pub fn from_bytes(key: [u8; 32]) -> Self {
        DiversifierKey(key)
    }

    /// Returns the byte representation of the diversifier key.
    pub fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }

    fn derive_child(&self, i_l: &[u8]) -> Self {
        let mut dk = [0u8; 32];
        dk.copy_from_slice(&PrfExpand::SAPLING_ZIP32_CHILD_DK.with(i_l, &self.0)[..32]);
        DiversifierKey(dk)
    }

    fn try_diversifier_internal(ff: &FF1<Aes256>, j: DiversifierIndex) -> Option<Diversifier> {
        // Generate d_j
        let enc = ff
            .encrypt(&[], &BinaryNumeralString::from_bytes_le(j.as_bytes()))
            .unwrap();
        let mut d_j = [0; 11];
        d_j.copy_from_slice(&enc.to_bytes_le());
        let diversifier = Diversifier(d_j);

        // validate that the generated diversifier maps to a jubjub subgroup point.
        diversifier.g_d().map(|_| diversifier)
    }

    /// Attempts to produce a diversifier at the given index. Returns None
    /// if the index does not produce a valid diversifier.
    pub fn diversifier(&self, j: DiversifierIndex) -> Option<Diversifier> {
        let ff = FF1::<Aes256>::new(&self.0, 2).unwrap();
        Self::try_diversifier_internal(&ff, j)
    }

    /// Returns the diversifier index to which this key maps the given diversifier.
    ///
    /// This method cannot be used to verify whether the diversifier was originally
    /// generated with this diversifier key, because all valid diversifiers can be
    /// produced by all diversifier keys.
    pub fn diversifier_index(&self, d: &Diversifier) -> DiversifierIndex {
        let ff = FF1::<Aes256>::new(&self.0, 2).unwrap();
        let dec = ff
            .decrypt(&[], &BinaryNumeralString::from_bytes_le(&d.0[..]))
            .unwrap();
        DiversifierIndex::from(<[u8; 11]>::try_from(&dec.to_bytes_le()[..]).unwrap())
    }

    /// Returns the first index starting from j that generates a valid
    /// diversifier, along with the corresponding diversifier. Returns
    /// `None` if the diversifier space contains no valid diversifiers
    /// at or above the specified diversifier index.
    pub fn find_diversifier(
        &self,
        mut j: DiversifierIndex,
    ) -> Option<(DiversifierIndex, Diversifier)> {
        let ff = FF1::<Aes256>::new(&self.0, 2).unwrap();
        loop {
            match Self::try_diversifier_internal(&ff, j) {
                Some(d_j) => return Some((j, d_j)),
                None => {
                    if j.increment().is_err() {
                        return None;
                    }
                }
            }
        }
    }
}

/// The derivation index associated with a key.
///
/// Master keys are never derived via the ZIP 32 child derivation process, but they have
/// an index in their encoding. This type allows the encoding to be represented, while
/// also enabling the derivation methods to only accept [`ChildIndex`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum KeyIndex {
    Master,
    Child(ChildIndex),
}

impl KeyIndex {
    fn new(depth: u8, i: u32) -> Option<Self> {
        match (depth == 0, i) {
            (true, 0) => Some(KeyIndex::Master),
            (false, _) => ChildIndex::from_index(i).map(KeyIndex::Child),
            _ => None,
        }
    }

    fn index(&self) -> u32 {
        match self {
            KeyIndex::Master => 0,
            KeyIndex::Child(i) => i.index(),
        }
    }
}

/// A Sapling extended spending key
#[derive(Clone)]
pub struct ExtendedSpendingKey {
    depth: u8,
    parent_fvk_tag: FvkTag,
    child_index: KeyIndex,
    chain_code: ChainCode,
    pub expsk: ExpandedSpendingKey,
    dk: DiversifierKey,
}

impl core::cmp::PartialEq for ExtendedSpendingKey {
    fn eq(&self, rhs: &ExtendedSpendingKey) -> bool {
        self.depth == rhs.depth
            && self.parent_fvk_tag == rhs.parent_fvk_tag
            && self.child_index == rhs.child_index
            && self.chain_code == rhs.chain_code
            && self.expsk.ask == rhs.expsk.ask
            && self.expsk.nsk == rhs.expsk.nsk
            && self.expsk.ovk == rhs.expsk.ovk
            && self.dk == rhs.dk
    }
}

impl core::fmt::Debug for ExtendedSpendingKey {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
        write!(
            f,
            "ExtendedSpendingKey(d = {}, tag_p = {:?}, i = {:?})",
            self.depth, self.parent_fvk_tag, self.child_index
        )
    }
}

impl ExtendedSpendingKey {
    pub fn master(seed: &[u8]) -> Self {
        let i = Blake2bParams::new()
            .hash_length(64)
            .personal(ZIP32_SAPLING_MASTER_PERSONALIZATION)
            .hash(seed);

        let sk_m = &i.as_bytes()[..32];
        let mut c_m = [0u8; 32];
        c_m.copy_from_slice(&i.as_bytes()[32..]);

        ExtendedSpendingKey {
            depth: 0,
            parent_fvk_tag: FvkTag::master(),
            child_index: KeyIndex::Master,
            chain_code: ChainCode::new(c_m),
            expsk: ExpandedSpendingKey::from_spending_key(sk_m),
            dk: DiversifierKey::master(sk_m),
        }
    }

    /// Decodes the extended spending key from its serialized representation as defined in
    /// [ZIP 32](https://zips.z.cash/zip-0032)
    pub fn from_bytes(b: &[u8]) -> Result<Self, DecodingError> {
        if b.len() != 169 {
            return Err(DecodingError::LengthInvalid {
                expected: 169,
                actual: b.len(),
            });
        }

        let depth = b[0];

        let mut parent_fvk_tag = FvkTag([0; 4]);
        parent_fvk_tag.0[..].copy_from_slice(&b[1..5]);

        let mut ci_bytes = [0u8; 4];
        ci_bytes[..].copy_from_slice(&b[5..9]);
        let child_index = KeyIndex::new(depth, u32::from_le_bytes(ci_bytes))
            .ok_or(DecodingError::UnsupportedChildIndex)?;

        let mut c = [0u8; 32];
        c[..].copy_from_slice(&b[9..41]);

        let expsk = ExpandedSpendingKey::from_bytes(&b[41..137])?;

        let mut dk = DiversifierKey([0u8; 32]);
        dk.0[..].copy_from_slice(&b[137..169]);

        Ok(ExtendedSpendingKey {
            depth,
            parent_fvk_tag,
            child_index,
            chain_code: ChainCode::new(c),
            expsk,
            dk,
        })
    }

    /// Reads and decodes the encoded form of the extended spending key as defined in
    /// [ZIP 32](https://zips.z.cash/zip-0032) from the provided reader.
    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
        let mut depth = [0; 1];
        reader.read_exact(&mut depth)?;
        let depth = depth[0];
        let mut tag = [0; 4];
        reader.read_exact(&mut tag)?;
        let mut child_index_bytes = [0; 4];
        reader.read_exact(&mut child_index_bytes)?;
        let child_index =
            KeyIndex::new(depth, u32::from_le_bytes(child_index_bytes)).ok_or_else(|| {
                corez::io::Error::new(
                    corez::io::ErrorKind::InvalidData,
                    "Unsupported child index in encoding",
                )
            })?;
        let mut c = [0; 32];
        reader.read_exact(&mut c)?;
        let expsk = ExpandedSpendingKey::read(&mut reader)?;
        let mut dk = [0; 32];
        reader.read_exact(&mut dk)?;

        Ok(ExtendedSpendingKey {
            depth,
            parent_fvk_tag: FvkTag(tag),
            child_index,
            chain_code: ChainCode::new(c),
            expsk,
            dk: DiversifierKey(dk),
        })
    }

    /// Encodes the extended spending key to its serialized representation as defined in
    /// [ZIP 32](https://zips.z.cash/zip-0032)
    pub fn to_bytes(&self) -> [u8; 169] {
        let mut result = [0u8; 169];
        result[0] = self.depth;
        result[1..5].copy_from_slice(&self.parent_fvk_tag.as_bytes()[..]);
        result[5..9].copy_from_slice(&self.child_index.index().to_le_bytes()[..]);
        result[9..41].copy_from_slice(&self.chain_code.as_bytes()[..]);
        result[41..137].copy_from_slice(&self.expsk.to_bytes()[..]);
        result[137..169].copy_from_slice(&self.dk.as_bytes()[..]);
        result
    }

    /// Writes the encoded form of the extended spending key as defined in
    /// [ZIP 32](https://zips.z.cash/zip-0032) to the provided writer.
    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
        writer.write_all(&self.to_bytes())
    }

    /// Returns the child key corresponding to the path derived from the master key
    pub fn from_path(master: &ExtendedSpendingKey, path: &[ChildIndex]) -> Self {
        let mut xsk = master.clone();
        for &i in path.iter() {
            xsk = xsk.derive_child(i);
        }
        xsk
    }

    /// Derives the child key at the given (hardened) index.
    ///
    /// # Panics
    ///
    /// Panics if the child key has `ask = 0`. This has a negligible probability of
    /// occurring.
    #[must_use]
    pub fn derive_child(&self, i: ChildIndex) -> Self {
        let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk);
        let tmp = {
            let le_i = i.index().to_le_bytes();
            PrfExpand::SAPLING_ZIP32_CHILD_HARDENED.with(
                self.chain_code.as_bytes(),
                &self.expsk.to_bytes(),
                &self.dk.0,
                &le_i,
            )
        };
        let i_l = &tmp[..32];
        let mut c_i = [0u8; 32];
        c_i.copy_from_slice(&tmp[32..]);

        ExtendedSpendingKey {
            depth: self.depth + 1,
            parent_fvk_tag: FvkFingerprint::from(&fvk).tag(),
            child_index: KeyIndex::Child(i),
            chain_code: ChainCode::new(c_i),
            expsk: {
                let mut ask =
                    jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_ZIP32_CHILD_I_ASK.with(i_l));
                let mut nsk =
                    jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_ZIP32_CHILD_I_NSK.with(i_l));
                ask.add_assign(self.expsk.ask.to_scalar());
                nsk.add_assign(&self.expsk.nsk);
                let ovk = derive_child_ovk(&self.expsk.ovk, i_l);
                ExpandedSpendingKey {
                    ask: SpendAuthorizingKey::from_scalar(ask)
                        .expect("negligible chance of ask == 0"),
                    nsk,
                    ovk,
                }
            },
            dk: self.dk.derive_child(i_l),
        }
    }

    /// Returns the address with the lowest valid diversifier index, along with
    /// the diversifier index that generated that address.
    pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
        self.to_diversifiable_full_viewing_key().default_address()
    }

    /// Derives an internal spending key given an external spending key.
    ///
    /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-spending-key).
    #[must_use]
    pub fn derive_internal(&self) -> Self {
        let i = {
            let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk);
            let mut h = Blake2bParams::new()
                .hash_length(32)
                .personal(ZIP32_SAPLING_INT_PERSONALIZATION)
                .to_state();
            h.update(&fvk.to_bytes());
            h.update(&self.dk.0);
            h.finalize()
        };
        let i_nsk =
            jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_ZIP32_INTERNAL_NSK.with(i.as_bytes()));
        let r = PrfExpand::SAPLING_ZIP32_INTERNAL_DK_OVK.with(i.as_bytes());
        let nsk_internal = i_nsk + self.expsk.nsk;
        let dk_internal = DiversifierKey(r[..32].try_into().unwrap());
        let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap());

        ExtendedSpendingKey {
            depth: self.depth,
            parent_fvk_tag: self.parent_fvk_tag,
            child_index: self.child_index,
            chain_code: self.chain_code,
            expsk: ExpandedSpendingKey {
                ask: self.expsk.ask.clone(),
                nsk: nsk_internal,
                ovk: ovk_internal,
            },
            dk: dk_internal,
        }
    }

    #[deprecated(note = "Use `to_diversifiable_full_viewing_key` instead.")]
    pub fn to_extended_full_viewing_key(&self) -> ExtendedFullViewingKey {
        ExtendedFullViewingKey {
            depth: self.depth,
            parent_fvk_tag: self.parent_fvk_tag,
            child_index: self.child_index,
            chain_code: self.chain_code,
            fvk: FullViewingKey::from_expanded_spending_key(&self.expsk),
            dk: self.dk,
        }
    }

    pub fn to_diversifiable_full_viewing_key(&self) -> DiversifiableFullViewingKey {
        DiversifiableFullViewingKey {
            fvk: FullViewingKey::from_expanded_spending_key(&self.expsk),
            dk: self.dk,
        }
    }
}

// A Sapling extended full viewing key
#[derive(Clone)]
pub struct ExtendedFullViewingKey {
    depth: u8,
    parent_fvk_tag: FvkTag,
    child_index: KeyIndex,
    chain_code: ChainCode,
    pub fvk: FullViewingKey,
    pub(crate) dk: DiversifierKey,
}

impl core::cmp::PartialEq for ExtendedFullViewingKey {
    fn eq(&self, rhs: &ExtendedFullViewingKey) -> bool {
        self.depth == rhs.depth
            && self.parent_fvk_tag == rhs.parent_fvk_tag
            && self.child_index == rhs.child_index
            && self.chain_code == rhs.chain_code
            && self.fvk.vk.ak == rhs.fvk.vk.ak
            && self.fvk.vk.nk == rhs.fvk.vk.nk
            && self.fvk.ovk == rhs.fvk.ovk
            && self.dk == rhs.dk
    }
}

impl core::fmt::Debug for ExtendedFullViewingKey {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
        write!(
            f,
            "ExtendedFullViewingKey(d = {}, tag_p = {:?}, i = {:?})",
            self.depth, self.parent_fvk_tag, self.child_index
        )
    }
}

impl ExtendedFullViewingKey {
    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
        let mut depth = [0; 1];
        reader.read_exact(&mut depth)?;
        let depth = depth[0];
        let mut tag = [0; 4];
        reader.read_exact(&mut tag)?;
        let mut child_index_bytes = [0; 4];
        reader.read_exact(&mut child_index_bytes)?;
        let child_index =
            KeyIndex::new(depth, u32::from_le_bytes(child_index_bytes)).ok_or_else(|| {
                corez::io::Error::new(
                    corez::io::ErrorKind::InvalidData,
                    "Unsupported child index in encoding",
                )
            })?;
        let mut c = [0; 32];
        reader.read_exact(&mut c)?;
        let fvk = FullViewingKey::read(&mut reader)?;
        let mut dk = [0; 32];
        reader.read_exact(&mut dk)?;

        Ok(ExtendedFullViewingKey {
            depth,
            parent_fvk_tag: FvkTag(tag),
            child_index,
            chain_code: ChainCode::new(c),
            fvk,
            dk: DiversifierKey(dk),
        })
    }

    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
        writer.write_all(&[self.depth])?;
        writer.write_all(&self.parent_fvk_tag.0)?;
        writer.write_all(&self.child_index.index().to_le_bytes())?;
        writer.write_all(self.chain_code.as_bytes())?;
        writer.write_all(&self.fvk.to_bytes())?;
        writer.write_all(&self.dk.0)?;

        Ok(())
    }

    /// Attempt to produce a payment address given the specified diversifier
    /// index, and return None if the specified index does not produce a valid
    /// diversifier.
    pub fn address(&self, j: DiversifierIndex) -> Option<PaymentAddress> {
        sapling_address(&self.fvk, &self.dk, j)
    }

    /// Search the diversifier space starting at diversifier index `j` for
    /// one which will produce a valid diversifier, and return the payment address
    /// constructed using that diversifier along with the index at which the
    /// valid diversifier was found.
    pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> {
        sapling_find_address(&self.fvk, &self.dk, j)
    }

    /// Returns the payment address corresponding to the smallest valid diversifier
    /// index, along with that index.
    pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
        sapling_default_address(&self.fvk, &self.dk)
    }

    /// Derives an internal full viewing key used for internal operations such
    /// as change and auto-shielding. The internal FVK has the same spend authority
    /// (the private key corresponding to ak) as the original, but viewing authority
    /// only for internal transfers.
    ///
    /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-full-viewing-key).
    #[must_use]
    pub fn derive_internal(&self) -> Self {
        let (fvk_internal, dk_internal) = sapling_derive_internal_fvk(&self.fvk, &self.dk);

        ExtendedFullViewingKey {
            depth: self.depth,
            parent_fvk_tag: self.parent_fvk_tag,
            child_index: self.child_index,
            chain_code: self.chain_code,
            fvk: fvk_internal,
            dk: dk_internal,
        }
    }

    pub fn to_diversifiable_full_viewing_key(&self) -> DiversifiableFullViewingKey {
        DiversifiableFullViewingKey {
            fvk: self.fvk.clone(),
            dk: self.dk,
        }
    }
}

/// A Sapling key that provides the capability to view incoming and outgoing transactions.
///
/// This key is useful anywhere you need to maintain accurate balance, but do not want the
/// ability to spend funds (such as a view-only wallet).
///
/// It comprises the subset of the ZIP 32 extended full viewing key that is used for the
/// Sapling item in a [ZIP 316 Unified Full Viewing Key][zip-0316-ufvk].
///
/// [zip-0316-ufvk]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiversifiableFullViewingKey {
    fvk: FullViewingKey,
    dk: DiversifierKey,
}

impl From<ExtendedFullViewingKey> for DiversifiableFullViewingKey {
    fn from(extfvk: ExtendedFullViewingKey) -> Self {
        DiversifiableFullViewingKey {
            fvk: extfvk.fvk,
            dk: extfvk.dk,
        }
    }
}

impl From<&ExtendedFullViewingKey> for DiversifiableFullViewingKey {
    fn from(extfvk: &ExtendedFullViewingKey) -> Self {
        extfvk.to_diversifiable_full_viewing_key()
    }
}

impl DiversifiableFullViewingKey {
    /// Parses a `DiversifiableFullViewingKey` from its raw byte encoding.
    ///
    /// Returns `None` if the bytes do not contain a valid encoding of a diversifiable
    /// Sapling full viewing key.
    pub fn from_bytes(bytes: &[u8; 128]) -> Option<Self> {
        FullViewingKey::read(&bytes[..96]).ok().map(|fvk| Self {
            fvk,
            dk: DiversifierKey::from_bytes(bytes[96..].try_into().unwrap()),
        })
    }

    /// Returns the raw encoding of this `DiversifiableFullViewingKey`.
    pub fn to_bytes(&self) -> [u8; 128] {
        let mut bytes = [0; 128];
        self.fvk
            .write(&mut bytes[..96])
            .expect("slice should be the correct length");
        bytes[96..].copy_from_slice(&self.dk.as_bytes()[..]);
        bytes
    }

    /// Derives the internal `DiversifiableFullViewingKey` corresponding to `self` (which
    /// is assumed here to be an external DFVK).
    fn derive_internal(&self) -> Self {
        let (fvk, dk) = sapling_derive_internal_fvk(&self.fvk, &self.dk);
        Self { fvk, dk }
    }

    /// Exposes the external [`FullViewingKey`] component of this diversifiable full viewing key.
    pub fn fvk(&self) -> &FullViewingKey {
        &self.fvk
    }

    /// Returns the internal [`FullViewingKey`] component of this diversifiable full viewing key.
    pub fn to_internal_fvk(&self) -> FullViewingKey {
        self.derive_internal().fvk
    }

    /// Derives a nullifier-deriving key for the provided scope.
    ///
    /// This API is provided so that nullifiers for change notes can be correctly computed.
    pub fn to_nk(&self, scope: Scope) -> NullifierDerivingKey {
        match scope {
            Scope::External => self.fvk.vk.nk,
            Scope::Internal => self.derive_internal().fvk.vk.nk,
        }
    }

    /// Derives an incoming viewing key corresponding to this full viewing key.
    // TODO: This should be renamed "prepare" and return a PreparedIncomingViewingKey
    pub fn to_ivk(&self, scope: Scope) -> SaplingIvk {
        match scope {
            Scope::External => self.fvk.vk.ivk(),
            Scope::Internal => self.derive_internal().fvk.vk.ivk(),
        }
    }

    /// Derives the external diversifiable incoming viewing key corresponding to this full viewing key.
    pub fn to_external_ivk(&self) -> IncomingViewingKey {
        IncomingViewingKey {
            dk: self.dk,
            ivk: self.to_ivk(Scope::External),
        }
    }

    /// Derives an outgoing viewing key corresponding to this full viewing key.
    pub fn to_ovk(&self, scope: Scope) -> OutgoingViewingKey {
        match scope {
            Scope::External => self.fvk.ovk,
            Scope::Internal => self.derive_internal().fvk.ovk,
        }
    }

    /// Attempts to produce a valid payment address for the given diversifier index.
    ///
    /// Returns `None` if the diversifier index does not produce a valid diversifier for
    /// this `DiversifiableFullViewingKey`.
    pub fn address(&self, j: DiversifierIndex) -> Option<PaymentAddress> {
        self.to_external_ivk().address_at(j)
    }

    /// Finds the next valid payment address starting from the given diversifier index.
    ///
    /// This searches the diversifier space starting at `j` and incrementing, to find an
    /// index which will produce a valid diversifier (a 50% probability for each index).
    ///
    /// Returns the index at which the valid diversifier was found along with the payment
    /// address constructed using that diversifier, or `None` if the maximum index was
    /// reached and no valid diversifier was found.
    pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> {
        self.to_external_ivk().find_address(j)
    }

    /// Returns the payment address corresponding to the smallest valid diversifier index,
    /// along with that index.
    pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
        self.to_external_ivk().find_address(0u32).unwrap()
    }

    /// Returns the payment address corresponding to the specified diversifier, if any.
    ///
    /// In general, it is preferable to use `find_address` instead, but this method is
    /// useful in some cases for matching keys to existing payment addresses.
    pub fn diversified_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
        self.to_external_ivk().address(diversifier)
    }

    /// Returns the internal address corresponding to the smallest valid diversifier index,
    /// along with that index.
    ///
    /// This address **MUST NOT** be encoded and exposed to end users. User interfaces
    /// should instead mark these notes as "change notes" or "internal wallet operations".
    pub fn change_address(&self) -> (DiversifierIndex, PaymentAddress) {
        let internal_dfvk = self.derive_internal();
        sapling_default_address(&internal_dfvk.fvk, &internal_dfvk.dk)
    }

    /// Returns the change address corresponding to the specified diversifier, if any.
    ///
    /// In general, it is preferable to use `change_address` instead, but this method is
    /// useful in some cases for matching keys to existing payment addresses.
    pub fn diversified_change_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
        self.derive_internal()
            .fvk
            .vk
            .to_payment_address(diversifier)
    }

    /// Attempts to decrypt the given address's diversifier with this full viewing key.
    ///
    /// This method extracts the diversifier from the given address and decrypts it as a
    /// diversifier index, then verifies that this diversifier index produces the same
    /// address. Decryption is attempted using both the internal and external parts of the
    /// full viewing key.
    ///
    /// Returns the decrypted diversifier index and its scope, or `None` if the address
    /// was not generated from this key.
    pub fn decrypt_diversifier(&self, addr: &PaymentAddress) -> Option<(DiversifierIndex, Scope)> {
        let j_external = self.dk.diversifier_index(addr.diversifier());
        if self.address(j_external).as_ref() == Some(addr) {
            return Some((j_external, Scope::External));
        }

        let j_internal = self
            .derive_internal()
            .dk
            .diversifier_index(addr.diversifier());
        if self.address(j_internal).as_ref() == Some(addr) {
            return Some((j_internal, Scope::Internal));
        }

        None
    }
}

/// A Sapling key that provides the capability to decrypt incoming notes and generate diversified
/// Sapling payment addresses.
///
/// This key is useful for detecting received funds and memos, but is not sufficient
/// for determining balance.
///
/// It comprises the subset of the ZIP 32 extended full viewing key that is used for the
/// Sapling item in a [ZIP 316 Unified Incoming Viewing Key][zip-0316-ufvk].
///
/// [zip-0316-ufvk]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IncomingViewingKey {
    dk: DiversifierKey,
    ivk: SaplingIvk,
}

impl IncomingViewingKey {
    /// Parses a `IncomingViewingKey` from its raw byte encoding.
    ///
    /// Returns `None` if the bytes do not contain a valid encoding of a diversifiable
    /// Sapling incoming viewing key.
    pub fn from_bytes(bytes: &[u8; 64]) -> CtOption<Self> {
        jubjub::Fr::from_bytes(&bytes[32..].try_into().unwrap()).map(|fr| IncomingViewingKey {
            dk: DiversifierKey::from_bytes(bytes[..32].try_into().unwrap()),
            ivk: SaplingIvk(fr),
        })
    }

    /// Returns the raw encoding of this `IncomingViewingKey`.
    pub fn to_bytes(&self) -> [u8; 64] {
        let mut bytes = [0; 64];
        bytes[..32].copy_from_slice(&self.dk.as_bytes()[..]);
        bytes[32..].copy_from_slice(&self.ivk.0.to_bytes()[..]);
        bytes
    }

    /// Returns the prepared incoming viewing key for this IVK.
    pub fn prepare(&self) -> PreparedIncomingViewingKey {
        PreparedIncomingViewingKey::new(&self.ivk)
    }

    /// Attempts to produce a valid payment address for the given diversifier index.
    ///
    /// Returns `None` if the diversifier index does not produce a valid diversifier for
    /// this `IncomingViewingKey`.
    pub fn address_at(&self, j: impl Into<DiversifierIndex>) -> Option<PaymentAddress> {
        self.dk
            .diversifier(j.into())
            .and_then(|d_j| self.ivk.to_payment_address(d_j))
    }

    /// Returns the payment address corresponding to the specified diversifier, if any.
    ///
    /// In general, it is preferable to use `find_address` instead, but this method is
    /// useful in some cases for matching keys to existing payment addresses.
    pub fn address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
        self.ivk.to_payment_address(diversifier)
    }

    /// Finds the next valid payment address starting from the given diversifier index.
    ///
    /// This searches the diversifier space starting at `j` and incrementing, to find an
    /// index which will produce a valid diversifier (a 50% probability for each index).
    ///
    /// Returns the index at which the valid diversifier was found along with the payment
    /// address constructed using that diversifier, or `None` if the maximum index was
    /// reached and no valid diversifier was found.
    pub fn find_address(
        &self,
        j: impl Into<DiversifierIndex>,
    ) -> Option<(DiversifierIndex, PaymentAddress)> {
        let (j, d_j) = self.dk.find_diversifier(j.into())?;
        self.ivk.to_payment_address(d_j).map(|addr| (j, addr))
    }

    /// Attempts to decrypt the given address's diversifier with this incoming viewing key.
    ///
    /// This method extracts the diversifier from the given address and decrypts it as a
    /// diversifier index, then verifies that this diversifier index produces the same
    /// address. Decryption is attempted using both the internal and external parts of the
    /// full viewing key.
    ///
    /// Returns the decrypted diversifier index and its scope, or `None` if the address
    /// was not generated from this key.
    pub fn decrypt_diversifier(&self, addr: &PaymentAddress) -> Option<DiversifierIndex> {
        let j = self.dk.diversifier_index(addr.diversifier());
        if self.address_at(j).as_ref() == Some(addr) {
            Some(j)
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use super::{DiversifiableFullViewingKey, ExtendedSpendingKey};
    use ff::PrimeField;
    use group::GroupEncoding;

    #[test]
    #[allow(deprecated)]
    fn derive_hardened_child() {
        let seed = [0; 32];
        let xsk_m = ExtendedSpendingKey::master(&seed);

        let i_5h = ChildIndex::hardened(5);
        let _ = xsk_m.derive_child(i_5h);
    }

    #[test]
    fn path() {
        let seed = [0; 32];
        let xsk_m = ExtendedSpendingKey::master(&seed);

        let xsk_5h = xsk_m.derive_child(ChildIndex::hardened(5));
        assert_eq!(
            ExtendedSpendingKey::from_path(&xsk_m, &[ChildIndex::hardened(5)]),
            xsk_5h
        );

        let xsk_5h_7 = xsk_5h.derive_child(ChildIndex::hardened(7));
        assert_eq!(
            ExtendedSpendingKey::from_path(
                &xsk_m,
                &[ChildIndex::hardened(5), ChildIndex::hardened(7)]
            ),
            xsk_5h_7
        );
    }

    #[test]
    fn diversifier() {
        let dk = DiversifierKey([0; 32]);
        let j_0 = DiversifierIndex::new();
        let j_1 = DiversifierIndex::from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        let j_2 = DiversifierIndex::from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        let j_3 = DiversifierIndex::from([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        // Computed using this Rust implementation
        let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140];
        let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34];

        // j = 0
        let d_j = dk.diversifier(j_0).unwrap();
        assert_eq!(d_j.0, d_0);
        assert_eq!(dk.diversifier_index(&Diversifier(d_0)), j_0);

        // j = 1
        assert_eq!(dk.diversifier(j_1), None);

        // j = 2
        assert_eq!(dk.diversifier(j_2), None);

        // j = 3
        let d_j = dk.diversifier(j_3).unwrap();
        assert_eq!(d_j.0, d_3);
        assert_eq!(dk.diversifier_index(&Diversifier(d_3)), j_3);
    }

    #[test]
    fn diversifier_index_from() {
        let di32: u32 = 0xa0b0c0d0;
        assert_eq!(
            DiversifierIndex::from(di32),
            DiversifierIndex::from([0xd0, 0xc0, 0xb0, 0xa0, 0, 0, 0, 0, 0, 0, 0])
        );
        let di64: u64 = 0x0102030405060708;
        assert_eq!(
            DiversifierIndex::from(di64),
            DiversifierIndex::from([8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0])
        );
    }

    #[test]
    fn find_diversifier() {
        let dk = DiversifierKey([0; 32]);
        let j_0 = DiversifierIndex::new();
        let j_1 = DiversifierIndex::from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        let j_2 = DiversifierIndex::from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        let j_3 = DiversifierIndex::from([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        // Computed using this Rust implementation
        let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140];
        let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34];

        // j = 0
        let (j, d_j) = dk.find_diversifier(j_0).unwrap();
        assert_eq!(j, j_0);
        assert_eq!(d_j.0, d_0);

        // j = 1
        let (j, d_j) = dk.find_diversifier(j_1).unwrap();
        assert_eq!(j, j_3);
        assert_eq!(d_j.0, d_3);

        // j = 2
        let (j, d_j) = dk.find_diversifier(j_2).unwrap();
        assert_eq!(j, j_3);
        assert_eq!(d_j.0, d_3);

        // j = 3
        let (j, d_j) = dk.find_diversifier(j_3).unwrap();
        assert_eq!(j, j_3);
        assert_eq!(d_j.0, d_3);
    }

    #[test]
    fn dfvk_round_trip() {
        let dfvk = {
            let extsk = ExtendedSpendingKey::master(&[]);
            #[allow(deprecated)]
            let extfvk = extsk.to_extended_full_viewing_key();
            DiversifiableFullViewingKey::from(extfvk)
        };

        // Check value -> bytes -> parsed round trip.
        let dfvk_bytes = dfvk.to_bytes();
        let dfvk_parsed = DiversifiableFullViewingKey::from_bytes(&dfvk_bytes).unwrap();
        assert_eq!(dfvk_parsed.fvk.vk.ak, dfvk.fvk.vk.ak);
        assert_eq!(dfvk_parsed.fvk.vk.nk, dfvk.fvk.vk.nk);
        assert_eq!(dfvk_parsed.fvk.ovk, dfvk.fvk.ovk);
        assert_eq!(dfvk_parsed.dk, dfvk.dk);

        // Check bytes -> parsed -> bytes round trip.
        assert_eq!(dfvk_parsed.to_bytes(), dfvk_bytes);
    }

    #[test]
    fn ivk_round_trip() {
        let ivk = {
            let extsk = ExtendedSpendingKey::master(&[]);
            #[allow(deprecated)]
            let extfvk = extsk.to_extended_full_viewing_key();
            DiversifiableFullViewingKey::from(extfvk).to_external_ivk()
        };

        // Check value -> bytes -> parsed round trip.
        let ivk_bytes = ivk.to_bytes();
        let ivk_parsed = IncomingViewingKey::from_bytes(&ivk_bytes).unwrap();
        assert_eq!(ivk_parsed.dk, ivk.dk);
        assert_eq!(ivk_parsed.ivk.0, ivk.ivk.0);

        // Check bytes -> parsed -> bytes round trip.
        assert_eq!(ivk_parsed.to_bytes(), ivk_bytes);
    }

    #[test]
    fn address() {
        let seed = [0; 32];
        let xsk_m = ExtendedSpendingKey::master(&seed);
        let xfvk_m = xsk_m.to_diversifiable_full_viewing_key();
        let j_0 = DiversifierIndex::new();
        let addr_m = xfvk_m.address(j_0).unwrap();
        assert_eq!(
            addr_m.diversifier().0,
            // Computed using this Rust implementation
            [59, 246, 250, 31, 131, 191, 69, 99, 200, 167, 19]
        );

        let j_1 = DiversifierIndex::from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        assert_eq!(xfvk_m.address(j_1), None);
    }

    #[test]
    fn default_address() {
        let seed = [0; 32];
        let xsk_m = ExtendedSpendingKey::master(&seed);
        let (j_m, addr_m) = xsk_m.default_address();
        assert_eq!(j_m.as_bytes(), &[0; 11]);
        assert_eq!(
            addr_m.diversifier().0,
            // Computed using this Rust implementation
            [59, 246, 250, 31, 131, 191, 69, 99, 200, 167, 19]
        );
    }

    #[test]
    #[allow(deprecated)]
    fn read_write() {
        let seed = [0; 32];
        let xsk = ExtendedSpendingKey::master(&seed);
        let fvk = xsk.to_extended_full_viewing_key();

        let mut ser = vec![];
        xsk.write(&mut ser).unwrap();
        let xsk2 = ExtendedSpendingKey::read(&ser[..]).unwrap();
        assert_eq!(xsk2, xsk);

        let mut ser = vec![];
        fvk.write(&mut ser).unwrap();
        let fvk2 = ExtendedFullViewingKey::read(&ser[..]).unwrap();
        assert_eq!(fvk2, fvk);
    }

    #[test]
    #[allow(deprecated)]
    fn test_vectors() {
        struct TestVector {
            ask: Option<[u8; 32]>,
            nsk: Option<[u8; 32]>,
            ovk: [u8; 32],
            dk: [u8; 32],
            c: [u8; 32],
            ak: [u8; 32],
            nk: [u8; 32],
            ivk: [u8; 32],
            xsk: Option<[u8; 169]>,
            xfvk: [u8; 169],
            fp: [u8; 32],
            d0: Option<[u8; 11]>,
            d1: Option<[u8; 11]>,
            d2: Option<[u8; 11]>,
            dmax: Option<[u8; 11]>,
            internal_nsk: Option<[u8; 32]>,
            internal_ovk: [u8; 32],
            internal_dk: [u8; 32],
            internal_nk: [u8; 32],
            internal_ivk: [u8; 32],
            internal_xsk: Option<[u8; 169]>,
            internal_xfvk: [u8; 169],
            internal_fp: [u8; 32],
        }

        // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py
        let test_vectors = [
            TestVector {
                ask: Some([
                    0xb6, 0xc0, 0x0c, 0x93, 0xd3, 0x60, 0x32, 0xb9, 0xa2, 0x68, 0xe9, 0x9e, 0x86,
                    0xa8, 0x60, 0x77, 0x65, 0x60, 0xbf, 0x0e, 0x83, 0xc1, 0xa1, 0x0b, 0x51, 0xf6,
                    0x07, 0xc9, 0x54, 0x74, 0x25, 0x06,
                ]),
                nsk: Some([
                    0x82, 0x04, 0xed, 0xe8, 0x3b, 0x2f, 0x1f, 0xbd, 0x84, 0xf9, 0xb4, 0x5d, 0x7f,
                    0x99, 0x6e, 0x2e, 0xbd, 0x0a, 0x03, 0x0a, 0xd2, 0x43, 0xb4, 0x8e, 0xd3, 0x9f,
                    0x74, 0x8a, 0x88, 0x21, 0xea, 0x06,
                ]),
                ovk: [
                    0x39, 0x58, 0x84, 0x89, 0x03, 0x23, 0xb9, 0xd4, 0x93, 0x3c, 0x02, 0x1d, 0xb8,
                    0x9b, 0xcf, 0x76, 0x7d, 0xf2, 0x19, 0x77, 0xb2, 0xff, 0x06, 0x83, 0x84, 0x83,
                    0x21, 0xa4, 0xdf, 0x4a, 0xfb, 0x21,
                ],
                dk: [
                    0x77, 0xc1, 0x7c, 0xb7, 0x5b, 0x77, 0x96, 0xaf, 0xb3, 0x9f, 0x0f, 0x3e, 0x91,
                    0xc9, 0x24, 0x60, 0x7d, 0xa5, 0x6f, 0xa9, 0xa2, 0x0e, 0x28, 0x35, 0x09, 0xbc,
                    0x8a, 0x3e, 0xf9, 0x96, 0xa1, 0x72,
                ],
                c: [
                    0xd0, 0x94, 0x7c, 0x4b, 0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27,
                    0x6d, 0x1c, 0xf3, 0xfd, 0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55,
                    0x0d, 0x75, 0x20, 0x18, 0x66, 0x8e,
                ],
                ak: [
                    0x93, 0x44, 0x2e, 0x5f, 0xef, 0xfb, 0xff, 0x16, 0xe7, 0x21, 0x72, 0x02, 0xdc,
                    0x73, 0x06, 0x72, 0x9f, 0xff, 0xfe, 0x85, 0xaf, 0x56, 0x83, 0xbc, 0xe2, 0x64,
                    0x2e, 0x3e, 0xeb, 0x5d, 0x38, 0x71,
                ],
                nk: [
                    0xdc, 0xe8, 0xe7, 0xed, 0xec, 0xe0, 0x4b, 0x89, 0x50, 0x41, 0x7f, 0x85, 0xba,
                    0x57, 0x69, 0x1b, 0x78, 0x3c, 0x45, 0xb1, 0xa2, 0x74, 0x22, 0xdb, 0x16, 0x93,
                    0xdc, 0xeb, 0x67, 0xb1, 0x01, 0x06,
                ],
                ivk: [
                    0x48, 0x47, 0xa1, 0x30, 0xe7, 0x99, 0xd3, 0xdb, 0xea, 0x36, 0xa1, 0xc1, 0x64,
                    0x67, 0xd6, 0x21, 0xfb, 0x2d, 0x80, 0xe3, 0x0b, 0x3b, 0x1d, 0x1a, 0x42, 0x68,
                    0x93, 0x41, 0x5d, 0xad, 0x66, 0x01,
                ],
                xsk: Some([
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b,
                    0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd,
                    0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18,
                    0x66, 0x8e, 0xb6, 0xc0, 0x0c, 0x93, 0xd3, 0x60, 0x32, 0xb9, 0xa2, 0x68, 0xe9,
                    0x9e, 0x86, 0xa8, 0x60, 0x77, 0x65, 0x60, 0xbf, 0x0e, 0x83, 0xc1, 0xa1, 0x0b,
                    0x51, 0xf6, 0x07, 0xc9, 0x54, 0x74, 0x25, 0x06, 0x82, 0x04, 0xed, 0xe8, 0x3b,
                    0x2f, 0x1f, 0xbd, 0x84, 0xf9, 0xb4, 0x5d, 0x7f, 0x99, 0x6e, 0x2e, 0xbd, 0x0a,
                    0x03, 0x0a, 0xd2, 0x43, 0xb4, 0x8e, 0xd3, 0x9f, 0x74, 0x8a, 0x88, 0x21, 0xea,
                    0x06, 0x39, 0x58, 0x84, 0x89, 0x03, 0x23, 0xb9, 0xd4, 0x93, 0x3c, 0x02, 0x1d,
                    0xb8, 0x9b, 0xcf, 0x76, 0x7d, 0xf2, 0x19, 0x77, 0xb2, 0xff, 0x06, 0x83, 0x84,
                    0x83, 0x21, 0xa4, 0xdf, 0x4a, 0xfb, 0x21, 0x77, 0xc1, 0x7c, 0xb7, 0x5b, 0x77,
                    0x96, 0xaf, 0xb3, 0x9f, 0x0f, 0x3e, 0x91, 0xc9, 0x24, 0x60, 0x7d, 0xa5, 0x6f,
                    0xa9, 0xa2, 0x0e, 0x28, 0x35, 0x09, 0xbc, 0x8a, 0x3e, 0xf9, 0x96, 0xa1, 0x72,
                ]),
                xfvk: [
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b,
                    0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd,
                    0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18,
                    0x66, 0x8e, 0x93, 0x44, 0x2e, 0x5f, 0xef, 0xfb, 0xff, 0x16, 0xe7, 0x21, 0x72,
                    0x02, 0xdc, 0x73, 0x06, 0x72, 0x9f, 0xff, 0xfe, 0x85, 0xaf, 0x56, 0x83, 0xbc,
                    0xe2, 0x64, 0x2e, 0x3e, 0xeb, 0x5d, 0x38, 0x71, 0xdc, 0xe8, 0xe7, 0xed, 0xec,
                    0xe0, 0x4b, 0x89, 0x50, 0x41, 0x7f, 0x85, 0xba, 0x57, 0x69, 0x1b, 0x78, 0x3c,
                    0x45, 0xb1, 0xa2, 0x74, 0x22, 0xdb, 0x16, 0x93, 0xdc, 0xeb, 0x67, 0xb1, 0x01,
                    0x06, 0x39, 0x58, 0x84, 0x89, 0x03, 0x23, 0xb9, 0xd4, 0x93, 0x3c, 0x02, 0x1d,
                    0xb8, 0x9b, 0xcf, 0x76, 0x7d, 0xf2, 0x19, 0x77, 0xb2, 0xff, 0x06, 0x83, 0x84,
                    0x83, 0x21, 0xa4, 0xdf, 0x4a, 0xfb, 0x21, 0x77, 0xc1, 0x7c, 0xb7, 0x5b, 0x77,
                    0x96, 0xaf, 0xb3, 0x9f, 0x0f, 0x3e, 0x91, 0xc9, 0x24, 0x60, 0x7d, 0xa5, 0x6f,
                    0xa9, 0xa2, 0x0e, 0x28, 0x35, 0x09, 0xbc, 0x8a, 0x3e, 0xf9, 0x96, 0xa1, 0x72,
                ],
                fp: [
                    0x14, 0xc2, 0x71, 0x3a, 0xdc, 0xe9, 0x3a, 0x83, 0x0e, 0xa8, 0x3a, 0x05, 0x19,
                    0x08, 0xb7, 0x44, 0x77, 0x83, 0xf5, 0xd1, 0x06, 0xc0, 0x98, 0x5e, 0x02, 0x55,
                    0x0e, 0x42, 0x6f, 0x27, 0x59, 0x7c,
                ],
                d0: Some([
                    0xd8, 0x62, 0x1b, 0x98, 0x1c, 0xf3, 0x00, 0xe9, 0xd4, 0xcc, 0x89,
                ]),
                d1: Some([
                    0x48, 0xea, 0x17, 0xa1, 0x99, 0xc8, 0x4b, 0xd1, 0xba, 0xa5, 0xd4,
                ]),
                d2: None,
                dmax: None,
                internal_nsk: Some([
                    0x51, 0x12, 0x33, 0x63, 0x6b, 0x95, 0xfd, 0x0a, 0xfb, 0x6b, 0xf8, 0x19, 0x3a,
                    0x7d, 0x8f, 0x49, 0xef, 0xd7, 0x36, 0xa9, 0x88, 0x77, 0x5c, 0x54, 0xf9, 0x56,
                    0x68, 0x76, 0x46, 0xea, 0xab, 0x07,
                ]),
                internal_ovk: [
                    0x9d, 0xc4, 0x77, 0xfe, 0x1e, 0x7d, 0x28, 0x29, 0x13, 0xf6, 0x51, 0x65, 0x4d,
                    0x39, 0x85, 0xf0, 0x9d, 0x53, 0xc2, 0xd3, 0xb5, 0x76, 0x3d, 0x7a, 0x72, 0x3b,
                    0xcb, 0xd6, 0xee, 0x05, 0x3d, 0x5a,
                ],
                internal_dk: [
                    0x40, 0xdd, 0xc5, 0x6e, 0x69, 0x75, 0x13, 0x8c, 0x08, 0x39, 0xe5, 0x80, 0xb5,
                    0x4d, 0x6d, 0x99, 0x9d, 0xc6, 0x16, 0x84, 0x3c, 0xfe, 0x04, 0x1e, 0x8f, 0x38,
                    0x8b, 0x12, 0x4e, 0xf7, 0xb5, 0xed,
                ],
                internal_nk: [
                    0xa3, 0x83, 0x1a, 0x5c, 0x69, 0x33, 0xf8, 0xec, 0x6a, 0xa5, 0xce, 0x31, 0x6c,
                    0x50, 0x8b, 0x79, 0x91, 0xcd, 0x94, 0xd3, 0xbd, 0xb7, 0x00, 0xa1, 0xc4, 0x27,
                    0xa6, 0xae, 0x15, 0xe7, 0x2f, 0xb5,
                ],
                internal_ivk: [
                    0x79, 0x05, 0x77, 0x32, 0x1c, 0x51, 0x18, 0x04, 0x63, 0x6e, 0xe6, 0xba, 0xa4,
                    0xee, 0xa7, 0x79, 0xb4, 0xa4, 0x6a, 0x5a, 0x12, 0xf8, 0x5d, 0x36, 0x50, 0x74,
                    0xa0, 0x9d, 0x05, 0x4f, 0x34, 0x01,
                ],
                internal_xsk: Some([
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b,
                    0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd,
                    0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18,
                    0x66, 0x8e, 0xb6, 0xc0, 0x0c, 0x93, 0xd3, 0x60, 0x32, 0xb9, 0xa2, 0x68, 0xe9,
                    0x9e, 0x86, 0xa8, 0x60, 0x77, 0x65, 0x60, 0xbf, 0x0e, 0x83, 0xc1, 0xa1, 0x0b,
                    0x51, 0xf6, 0x07, 0xc9, 0x54, 0x74, 0x25, 0x06, 0x51, 0x12, 0x33, 0x63, 0x6b,
                    0x95, 0xfd, 0x0a, 0xfb, 0x6b, 0xf8, 0x19, 0x3a, 0x7d, 0x8f, 0x49, 0xef, 0xd7,
                    0x36, 0xa9, 0x88, 0x77, 0x5c, 0x54, 0xf9, 0x56, 0x68, 0x76, 0x46, 0xea, 0xab,
                    0x07, 0x9d, 0xc4, 0x77, 0xfe, 0x1e, 0x7d, 0x28, 0x29, 0x13, 0xf6, 0x51, 0x65,
                    0x4d, 0x39, 0x85, 0xf0, 0x9d, 0x53, 0xc2, 0xd3, 0xb5, 0x76, 0x3d, 0x7a, 0x72,
                    0x3b, 0xcb, 0xd6, 0xee, 0x05, 0x3d, 0x5a, 0x40, 0xdd, 0xc5, 0x6e, 0x69, 0x75,
                    0x13, 0x8c, 0x08, 0x39, 0xe5, 0x80, 0xb5, 0x4d, 0x6d, 0x99, 0x9d, 0xc6, 0x16,
                    0x84, 0x3c, 0xfe, 0x04, 0x1e, 0x8f, 0x38, 0x8b, 0x12, 0x4e, 0xf7, 0xb5, 0xed,
                ]),
                internal_xfvk: [
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b,
                    0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd,
                    0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18,
                    0x66, 0x8e, 0x93, 0x44, 0x2e, 0x5f, 0xef, 0xfb, 0xff, 0x16, 0xe7, 0x21, 0x72,
                    0x02, 0xdc, 0x73, 0x06, 0x72, 0x9f, 0xff, 0xfe, 0x85, 0xaf, 0x56, 0x83, 0xbc,
                    0xe2, 0x64, 0x2e, 0x3e, 0xeb, 0x5d, 0x38, 0x71, 0xa3, 0x83, 0x1a, 0x5c, 0x69,
                    0x33, 0xf8, 0xec, 0x6a, 0xa5, 0xce, 0x31, 0x6c, 0x50, 0x8b, 0x79, 0x91, 0xcd,
                    0x94, 0xd3, 0xbd, 0xb7, 0x00, 0xa1, 0xc4, 0x27, 0xa6, 0xae, 0x15, 0xe7, 0x2f,
                    0xb5, 0x9d, 0xc4, 0x77, 0xfe, 0x1e, 0x7d, 0x28, 0x29, 0x13, 0xf6, 0x51, 0x65,
                    0x4d, 0x39, 0x85, 0xf0, 0x9d, 0x53, 0xc2, 0xd3, 0xb5, 0x76, 0x3d, 0x7a, 0x72,
                    0x3b, 0xcb, 0xd6, 0xee, 0x05, 0x3d, 0x5a, 0x40, 0xdd, 0xc5, 0x6e, 0x69, 0x75,
                    0x13, 0x8c, 0x08, 0x39, 0xe5, 0x80, 0xb5, 0x4d, 0x6d, 0x99, 0x9d, 0xc6, 0x16,
                    0x84, 0x3c, 0xfe, 0x04, 0x1e, 0x8f, 0x38, 0x8b, 0x12, 0x4e, 0xf7, 0xb5, 0xed,
                ],
                internal_fp: [
                    0x82, 0x64, 0xed, 0xec, 0x63, 0xb1, 0x55, 0x00, 0x1d, 0x84, 0x96, 0x68, 0x5c,
                    0xc7, 0xc2, 0x1e, 0xa9, 0x57, 0xc6, 0xf5, 0x91, 0x09, 0x0a, 0x1c, 0x20, 0xe5,
                    0x2a, 0x41, 0x89, 0xb8, 0xbb, 0x96,
                ],
            },
            TestVector {
                ask: Some([
                    0xd5, 0xf7, 0xe9, 0x2e, 0xfb, 0x7a, 0xbe, 0x04, 0xdc, 0x8c, 0x14, 0x8b, 0x0b,
                    0x3b, 0x0f, 0xc2, 0x3e, 0x04, 0x29, 0xf0, 0x02, 0x08, 0xff, 0x93, 0xb6, 0x8d,
                    0x21, 0xa6, 0xe1, 0x31, 0xbd, 0x04,
                ]),
                nsk: Some([
                    0x37, 0x2a, 0x7c, 0x68, 0x22, 0xcb, 0xe6, 0x03, 0xf3, 0x46, 0x5c, 0x4b, 0x9b,
                    0x65, 0x58, 0xf3, 0xa3, 0x51, 0x2d, 0xec, 0xd4, 0x34, 0x01, 0x2e, 0x67, 0xbf,
                    0xfc, 0xf6, 0x57, 0xe5, 0x75, 0x0a,
                ]),
                ovk: [
                    0x25, 0x30, 0x76, 0x19, 0x33, 0x34, 0x8c, 0x1f, 0xcf, 0x14, 0x35, 0x54, 0x33,
                    0xa8, 0xd2, 0x91, 0x16, 0x7f, 0xbb, 0x37, 0xb2, 0xce, 0x37, 0xca, 0x97, 0x16,
                    0x0a, 0x47, 0xec, 0x33, 0x1c, 0x69,
                ],
                dk: [
                    0xf2, 0x88, 0x40, 0x0f, 0xd6, 0x5f, 0x9a, 0xdf, 0xe3, 0xa7, 0xc3, 0x72, 0x0a,
                    0xce, 0xee, 0x0d, 0xae, 0x05, 0x0d, 0x0a, 0x81, 0x9d, 0x61, 0x9f, 0x92, 0xe9,
                    0xe2, 0xcb, 0x44, 0x34, 0xd5, 0x26,
                ],
                c: [
                    0x6f, 0xcc, 0xaa, 0x45, 0xa8, 0x20, 0x6b, 0x06, 0x3e, 0xbb, 0x68, 0xc6, 0x10,
                    0xe0, 0x59, 0x27, 0xaa, 0x94, 0xd6, 0x1b, 0xe9, 0x3e, 0xc2, 0x5e, 0xb4, 0xf8,
                    0x2e, 0xfd, 0x68, 0xca, 0xae, 0xdb,
                ],
                ak: [
                    0xcf, 0xca, 0x79, 0xd3, 0x37, 0xbc, 0x68, 0x98, 0x13, 0xe4, 0x09, 0xa5, 0x4e,
                    0x3e, 0x72, 0xad, 0x8e, 0x2f, 0x70, 0x3a, 0xe6, 0xf8, 0x22, 0x3c, 0x9b, 0xec,
                    0xbd, 0xe9, 0xa8, 0xa3, 0x5f, 0x53,
                ],
                nk: [
                    0x51, 0x3d, 0xe6, 0x40, 0x85, 0xd3, 0x5a, 0x3a, 0xdf, 0x23, 0xd8, 0x9d, 0x5a,
                    0x21, 0xcd, 0xee, 0x4d, 0xb4, 0xc6, 0x25, 0xbd, 0x6a, 0x3c, 0x3c, 0x62, 0x4b,
                    0xef, 0x43, 0x44, 0x14, 0x1d, 0xeb,
                ],
                ivk: [
                    0xf6, 0xe7, 0x5c, 0xd9, 0x80, 0xc3, 0x0e, 0xab, 0xc6, 0x1f, 0x49, 0xac, 0x68,
                    0xf4, 0x88, 0x57, 0x3a, 0xb3, 0xe6, 0xaf, 0xe1, 0x53, 0x76, 0x37, 0x5d, 0x34,
                    0xe4, 0x06, 0x70, 0x2f, 0xfd, 0x02,
                ],
                xsk: Some([
                    0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x80, 0x6f, 0xcc, 0xaa, 0x45,
                    0xa8, 0x20, 0x6b, 0x06, 0x3e, 0xbb, 0x68, 0xc6, 0x10, 0xe0, 0x59, 0x27, 0xaa,
                    0x94, 0xd6, 0x1b, 0xe9, 0x3e, 0xc2, 0x5e, 0xb4, 0xf8, 0x2e, 0xfd, 0x68, 0xca,
                    0xae, 0xdb, 0xd5, 0xf7, 0xe9, 0x2e, 0xfb, 0x7a, 0xbe, 0x04, 0xdc, 0x8c, 0x14,
                    0x8b, 0x0b, 0x3b, 0x0f, 0xc2, 0x3e, 0x04, 0x29, 0xf0, 0x02, 0x08, 0xff, 0x93,
                    0xb6, 0x8d, 0x21, 0xa6, 0xe1, 0x31, 0xbd, 0x04, 0x37, 0x2a, 0x7c, 0x68, 0x22,
                    0xcb, 0xe6, 0x03, 0xf3, 0x46, 0x5c, 0x4b, 0x9b, 0x65, 0x58, 0xf3, 0xa3, 0x51,
                    0x2d, 0xec, 0xd4, 0x34, 0x01, 0x2e, 0x67, 0xbf, 0xfc, 0xf6, 0x57, 0xe5, 0x75,
                    0x0a, 0x25, 0x30, 0x76, 0x19, 0x33, 0x34, 0x8c, 0x1f, 0xcf, 0x14, 0x35, 0x54,
                    0x33, 0xa8, 0xd2, 0x91, 0x16, 0x7f, 0xbb, 0x37, 0xb2, 0xce, 0x37, 0xca, 0x97,
                    0x16, 0x0a, 0x47, 0xec, 0x33, 0x1c, 0x69, 0xf2, 0x88, 0x40, 0x0f, 0xd6, 0x5f,
                    0x9a, 0xdf, 0xe3, 0xa7, 0xc3, 0x72, 0x0a, 0xce, 0xee, 0x0d, 0xae, 0x05, 0x0d,
                    0x0a, 0x81, 0x9d, 0x61, 0x9f, 0x92, 0xe9, 0xe2, 0xcb, 0x44, 0x34, 0xd5, 0x26,
                ]),
                xfvk: [
                    0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x80, 0x6f, 0xcc, 0xaa, 0x45,
                    0xa8, 0x20, 0x6b, 0x06, 0x3e, 0xbb, 0x68, 0xc6, 0x10, 0xe0, 0x59, 0x27, 0xaa,
                    0x94, 0xd6, 0x1b, 0xe9, 0x3e, 0xc2, 0x5e, 0xb4, 0xf8, 0x2e, 0xfd, 0x68, 0xca,
                    0xae, 0xdb, 0xcf, 0xca, 0x79, 0xd3, 0x37, 0xbc, 0x68, 0x98, 0x13, 0xe4, 0x09,
                    0xa5, 0x4e, 0x3e, 0x72, 0xad, 0x8e, 0x2f, 0x70, 0x3a, 0xe6, 0xf8, 0x22, 0x3c,
                    0x9b, 0xec, 0xbd, 0xe9, 0xa8, 0xa3, 0x5f, 0x53, 0x51, 0x3d, 0xe6, 0x40, 0x85,
                    0xd3, 0x5a, 0x3a, 0xdf, 0x23, 0xd8, 0x9d, 0x5a, 0x21, 0xcd, 0xee, 0x4d, 0xb4,
                    0xc6, 0x25, 0xbd, 0x6a, 0x3c, 0x3c, 0x62, 0x4b, 0xef, 0x43, 0x44, 0x14, 0x1d,
                    0xeb, 0x25, 0x30, 0x76, 0x19, 0x33, 0x34, 0x8c, 0x1f, 0xcf, 0x14, 0x35, 0x54,
                    0x33, 0xa8, 0xd2, 0x91, 0x16, 0x7f, 0xbb, 0x37, 0xb2, 0xce, 0x37, 0xca, 0x97,
                    0x16, 0x0a, 0x47, 0xec, 0x33, 0x1c, 0x69, 0xf2, 0x88, 0x40, 0x0f, 0xd6, 0x5f,
                    0x9a, 0xdf, 0xe3, 0xa7, 0xc3, 0x72, 0x0a, 0xce, 0xee, 0x0d, 0xae, 0x05, 0x0d,
                    0x0a, 0x81, 0x9d, 0x61, 0x9f, 0x92, 0xe9, 0xe2, 0xcb, 0x44, 0x34, 0xd5, 0x26,
                ],
                fp: [
                    0x76, 0x84, 0x23, 0xcb, 0x88, 0xd2, 0x2d, 0xee, 0x91, 0xb5, 0xb7, 0x66, 0x1e,
                    0x72, 0xed, 0x00, 0x95, 0x57, 0xeb, 0xa1, 0x44, 0xc7, 0x8d, 0x1a, 0xa7, 0x1a,
                    0x3e, 0x88, 0xb6, 0x91, 0x06, 0x96,
                ],
                d0: None,
                d1: Some([
                    0xbc, 0xc3, 0x23, 0xe8, 0xda, 0x39, 0xb4, 0x96, 0xc0, 0x50, 0x51,
                ]),
                d2: None,
                dmax: Some([
                    0x25, 0x14, 0x32, 0x0d, 0x33, 0x9c, 0x66, 0x6a, 0x25, 0x4c, 0x06,
                ]),
                internal_nsk: Some([
                    0x5d, 0x47, 0x0f, 0x97, 0x79, 0xcc, 0x25, 0x7f, 0x21, 0x28, 0x8f, 0x50, 0x5a,
                    0x4e, 0x65, 0xb3, 0x8e, 0xb8, 0x53, 0xf1, 0xa2, 0x45, 0x63, 0xb9, 0xf6, 0x74,
                    0x17, 0x26, 0xf4, 0xd3, 0x01, 0x03,
                ]),
                internal_ovk: [
                    0x78, 0x64, 0xe8, 0xc7, 0x9c, 0xea, 0xab, 0x97, 0xe6, 0xae, 0x5b, 0xca, 0x10,
                    0xf7, 0xd5, 0x1d, 0xf4, 0x20, 0x9a, 0xd0, 0xd4, 0x6e, 0x80, 0xac, 0x18, 0x0d,
                    0x50, 0xd3, 0xff, 0x09, 0xa6, 0x70,
                ],
                internal_dk: [
                    0x54, 0xf1, 0x1e, 0x3f, 0xa3, 0x0d, 0x34, 0xd6, 0xa7, 0x4d, 0xef, 0x1e, 0x6d,
                    0x5d, 0xaf, 0x58, 0xcf, 0xc7, 0xd7, 0x8b, 0x27, 0xcb, 0x07, 0x15, 0xc1, 0xaf,
                    0xfa, 0x29, 0xae, 0x39, 0x92, 0xfa,
                ],
                internal_nk: [
                    0x31, 0x42, 0x48, 0x75, 0xd6, 0xa5, 0xed, 0x75, 0xde, 0x20, 0x0b, 0xb5, 0xc1,
                    0xd8, 0x1a, 0xec, 0x4d, 0xff, 0x16, 0x50, 0xb7, 0x8b, 0xb0, 0xca, 0xde, 0x3c,
                    0x8c, 0x7a, 0xb0, 0x3d, 0xf1, 0x11,
                ],
                internal_ivk: [
                    0xe5, 0x42, 0x6b, 0x5b, 0x80, 0xb1, 0x18, 0x67, 0x97, 0x01, 0x65, 0x80, 0xc1,
                    0xf4, 0x1c, 0x34, 0x19, 0x68, 0x3a, 0xac, 0x77, 0xcf, 0x8d, 0xe0, 0x2f, 0x2f,
                    0x98, 0x07, 0xd1, 0x50, 0xb4, 0x02,
                ],
                internal_xsk: Some([
                    0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x80, 0x6f, 0xcc, 0xaa, 0x45,
                    0xa8, 0x20, 0x6b, 0x06, 0x3e, 0xbb, 0x68, 0xc6, 0x10, 0xe0, 0x59, 0x27, 0xaa,
                    0x94, 0xd6, 0x1b, 0xe9, 0x3e, 0xc2, 0x5e, 0xb4, 0xf8, 0x2e, 0xfd, 0x68, 0xca,
                    0xae, 0xdb, 0xd5, 0xf7, 0xe9, 0x2e, 0xfb, 0x7a, 0xbe, 0x04, 0xdc, 0x8c, 0x14,
                    0x8b, 0x0b, 0x3b, 0x0f, 0xc2, 0x3e, 0x04, 0x29, 0xf0, 0x02, 0x08, 0xff, 0x93,
                    0xb6, 0x8d, 0x21, 0xa6, 0xe1, 0x31, 0xbd, 0x04, 0x5d, 0x47, 0x0f, 0x97, 0x79,
                    0xcc, 0x25, 0x7f, 0x21, 0x28, 0x8f, 0x50, 0x5a, 0x4e, 0x65, 0xb3, 0x8e, 0xb8,
                    0x53, 0xf1, 0xa2, 0x45, 0x63, 0xb9, 0xf6, 0x74, 0x17, 0x26, 0xf4, 0xd3, 0x01,
                    0x03, 0x78, 0x64, 0xe8, 0xc7, 0x9c, 0xea, 0xab, 0x97, 0xe6, 0xae, 0x5b, 0xca,
                    0x10, 0xf7, 0xd5, 0x1d, 0xf4, 0x20, 0x9a, 0xd0, 0xd4, 0x6e, 0x80, 0xac, 0x18,
                    0x0d, 0x50, 0xd3, 0xff, 0x09, 0xa6, 0x70, 0x54, 0xf1, 0x1e, 0x3f, 0xa3, 0x0d,
                    0x34, 0xd6, 0xa7, 0x4d, 0xef, 0x1e, 0x6d, 0x5d, 0xaf, 0x58, 0xcf, 0xc7, 0xd7,
                    0x8b, 0x27, 0xcb, 0x07, 0x15, 0xc1, 0xaf, 0xfa, 0x29, 0xae, 0x39, 0x92, 0xfa,
                ]),
                internal_xfvk: [
                    0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x80, 0x6f, 0xcc, 0xaa, 0x45,
                    0xa8, 0x20, 0x6b, 0x06, 0x3e, 0xbb, 0x68, 0xc6, 0x10, 0xe0, 0x59, 0x27, 0xaa,
                    0x94, 0xd6, 0x1b, 0xe9, 0x3e, 0xc2, 0x5e, 0xb4, 0xf8, 0x2e, 0xfd, 0x68, 0xca,
                    0xae, 0xdb, 0xcf, 0xca, 0x79, 0xd3, 0x37, 0xbc, 0x68, 0x98, 0x13, 0xe4, 0x09,
                    0xa5, 0x4e, 0x3e, 0x72, 0xad, 0x8e, 0x2f, 0x70, 0x3a, 0xe6, 0xf8, 0x22, 0x3c,
                    0x9b, 0xec, 0xbd, 0xe9, 0xa8, 0xa3, 0x5f, 0x53, 0x31, 0x42, 0x48, 0x75, 0xd6,
                    0xa5, 0xed, 0x75, 0xde, 0x20, 0x0b, 0xb5, 0xc1, 0xd8, 0x1a, 0xec, 0x4d, 0xff,
                    0x16, 0x50, 0xb7, 0x8b, 0xb0, 0xca, 0xde, 0x3c, 0x8c, 0x7a, 0xb0, 0x3d, 0xf1,
                    0x11, 0x78, 0x64, 0xe8, 0xc7, 0x9c, 0xea, 0xab, 0x97, 0xe6, 0xae, 0x5b, 0xca,
                    0x10, 0xf7, 0xd5, 0x1d, 0xf4, 0x20, 0x9a, 0xd0, 0xd4, 0x6e, 0x80, 0xac, 0x18,
                    0x0d, 0x50, 0xd3, 0xff, 0x09, 0xa6, 0x70, 0x54, 0xf1, 0x1e, 0x3f, 0xa3, 0x0d,
                    0x34, 0xd6, 0xa7, 0x4d, 0xef, 0x1e, 0x6d, 0x5d, 0xaf, 0x58, 0xcf, 0xc7, 0xd7,
                    0x8b, 0x27, 0xcb, 0x07, 0x15, 0xc1, 0xaf, 0xfa, 0x29, 0xae, 0x39, 0x92, 0xfa,
                ],
                internal_fp: [
                    0x12, 0x87, 0xc3, 0x79, 0x02, 0x35, 0x69, 0x16, 0x4e, 0xb5, 0x23, 0xff, 0xdd,
                    0x72, 0x03, 0x1c, 0x35, 0xe1, 0x85, 0x9f, 0x3e, 0xf8, 0xd4, 0x83, 0x0a, 0x29,
                    0x91, 0xba, 0x7e, 0x15, 0xde, 0x2d,
                ],
            },
            TestVector {
                ask: Some([
                    0x7f, 0xf3, 0x5d, 0xb6, 0x9e, 0x13, 0xc3, 0x6f, 0x59, 0xad, 0x9c, 0x08, 0xd3,
                    0x2d, 0x52, 0x27, 0x37, 0x8d, 0xa0, 0xcf, 0xf9, 0x71, 0xfd, 0x42, 0x4b, 0xae,
                    0xf9, 0xa6, 0x33, 0x2f, 0x51, 0x06,
                ]),
                nsk: Some([
                    0x77, 0x9c, 0x6e, 0xe4, 0xa0, 0x39, 0x44, 0xeb, 0xa2, 0x8b, 0xc9, 0xbd, 0xc1,
                    0x32, 0x9a, 0x39, 0x14, 0x07, 0xf4, 0x8c, 0x41, 0x0d, 0x5a, 0xe0, 0xa3, 0x64,
                    0xf5, 0x99, 0x59, 0xbf, 0xde, 0x00,
                ]),
                ovk: [
                    0xd9, 0xfc, 0x71, 0x01, 0xbf, 0x90, 0x7f, 0x41, 0x88, 0x6a, 0x73, 0x30, 0xa5,
                    0xd6, 0xa7, 0xbd, 0x23, 0x53, 0x5e, 0x30, 0x5e, 0xb7, 0x67, 0x9b, 0xc2, 0x3d,
                    0x76, 0x05, 0x93, 0x61, 0x85, 0xac,
                ],
                dk: [
                    0xe4, 0x69, 0x9e, 0x9a, 0x86, 0xe0, 0x31, 0xc5, 0x4b, 0x21, 0xcd, 0xd0, 0x96,
                    0x0a, 0xc1, 0x8d, 0xdd, 0x61, 0xec, 0x9f, 0x7a, 0xe9, 0x8d, 0x55, 0x82, 0xa6,
                    0xfa, 0xf6, 0x5f, 0x32, 0x48, 0xd1,
                ],
                c: [
                    0x44, 0x79, 0x08, 0x6c, 0x75, 0xd0, 0x80, 0x79, 0x60, 0x20, 0xf5, 0x00, 0xc1,
                    0xe3, 0x0a, 0x54, 0xcf, 0xe2, 0x9d, 0xda, 0x36, 0xf2, 0x14, 0x4f, 0xb3, 0x3a,
                    0x50, 0x80, 0x6f, 0xbe, 0xf7, 0xda,
                ],
                ak: [
                    0x9a, 0x85, 0x3f, 0x95, 0x44, 0x71, 0x37, 0x97, 0xe0, 0x85, 0x17, 0x64, 0xda,
                    0x39, 0x2e, 0x68, 0x53, 0x4b, 0x1d, 0x94, 0x8d, 0xae, 0x47, 0x42, 0xee, 0x76,
                    0x5c, 0x72, 0x75, 0x72, 0xab, 0x4e,
                ],
                nk: [
                    0xf1, 0x66, 0xa2, 0x8a, 0x4f, 0x88, 0xce, 0xc1, 0x21, 0x41, 0xa8, 0x2d, 0x21,
                    0x20, 0xbd, 0x6d, 0x8c, 0xaf, 0x87, 0x9c, 0x9a, 0x1b, 0x3a, 0xd2, 0x11, 0x85,
                    0x01, 0x36, 0x4f, 0x5d, 0x4f, 0xbe,
                ],
                ivk: [
                    0x33, 0xbd, 0x46, 0x01, 0x5a, 0x2c, 0xad, 0x17, 0xd6, 0xe0, 0x15, 0xeb, 0x88,
                    0x86, 0x1b, 0x0c, 0x91, 0x77, 0x96, 0x24, 0x65, 0x70, 0x52, 0x1c, 0x9e, 0x1a,
                    0xe4, 0xb1, 0xc8, 0x31, 0x1d, 0x06,
                ],
                xsk: Some([
                    0x02, 0x76, 0x84, 0x23, 0xcb, 0x02, 0x00, 0x00, 0x80, 0x44, 0x79, 0x08, 0x6c,
                    0x75, 0xd0, 0x80, 0x79, 0x60, 0x20, 0xf5, 0x00, 0xc1, 0xe3, 0x0a, 0x54, 0xcf,
                    0xe2, 0x9d, 0xda, 0x36, 0xf2, 0x14, 0x4f, 0xb3, 0x3a, 0x50, 0x80, 0x6f, 0xbe,
                    0xf7, 0xda, 0x7f, 0xf3, 0x5d, 0xb6, 0x9e, 0x13, 0xc3, 0x6f, 0x59, 0xad, 0x9c,
                    0x08, 0xd3, 0x2d, 0x52, 0x27, 0x37, 0x8d, 0xa0, 0xcf, 0xf9, 0x71, 0xfd, 0x42,
                    0x4b, 0xae, 0xf9, 0xa6, 0x33, 0x2f, 0x51, 0x06, 0x77, 0x9c, 0x6e, 0xe4, 0xa0,
                    0x39, 0x44, 0xeb, 0xa2, 0x8b, 0xc9, 0xbd, 0xc1, 0x32, 0x9a, 0x39, 0x14, 0x07,
                    0xf4, 0x8c, 0x41, 0x0d, 0x5a, 0xe0, 0xa3, 0x64, 0xf5, 0x99, 0x59, 0xbf, 0xde,
                    0x00, 0xd9, 0xfc, 0x71, 0x01, 0xbf, 0x90, 0x7f, 0x41, 0x88, 0x6a, 0x73, 0x30,
                    0xa5, 0xd6, 0xa7, 0xbd, 0x23, 0x53, 0x5e, 0x30, 0x5e, 0xb7, 0x67, 0x9b, 0xc2,
                    0x3d, 0x76, 0x05, 0x93, 0x61, 0x85, 0xac, 0xe4, 0x69, 0x9e, 0x9a, 0x86, 0xe0,
                    0x31, 0xc5, 0x4b, 0x21, 0xcd, 0xd0, 0x96, 0x0a, 0xc1, 0x8d, 0xdd, 0x61, 0xec,
                    0x9f, 0x7a, 0xe9, 0x8d, 0x55, 0x82, 0xa6, 0xfa, 0xf6, 0x5f, 0x32, 0x48, 0xd1,
                ]),
                xfvk: [
                    0x02, 0x76, 0x84, 0x23, 0xcb, 0x02, 0x00, 0x00, 0x80, 0x44, 0x79, 0x08, 0x6c,
                    0x75, 0xd0, 0x80, 0x79, 0x60, 0x20, 0xf5, 0x00, 0xc1, 0xe3, 0x0a, 0x54, 0xcf,
                    0xe2, 0x9d, 0xda, 0x36, 0xf2, 0x14, 0x4f, 0xb3, 0x3a, 0x50, 0x80, 0x6f, 0xbe,
                    0xf7, 0xda, 0x9a, 0x85, 0x3f, 0x95, 0x44, 0x71, 0x37, 0x97, 0xe0, 0x85, 0x17,
                    0x64, 0xda, 0x39, 0x2e, 0x68, 0x53, 0x4b, 0x1d, 0x94, 0x8d, 0xae, 0x47, 0x42,
                    0xee, 0x76, 0x5c, 0x72, 0x75, 0x72, 0xab, 0x4e, 0xf1, 0x66, 0xa2, 0x8a, 0x4f,
                    0x88, 0xce, 0xc1, 0x21, 0x41, 0xa8, 0x2d, 0x21, 0x20, 0xbd, 0x6d, 0x8c, 0xaf,
                    0x87, 0x9c, 0x9a, 0x1b, 0x3a, 0xd2, 0x11, 0x85, 0x01, 0x36, 0x4f, 0x5d, 0x4f,
                    0xbe, 0xd9, 0xfc, 0x71, 0x01, 0xbf, 0x90, 0x7f, 0x41, 0x88, 0x6a, 0x73, 0x30,
                    0xa5, 0xd6, 0xa7, 0xbd, 0x23, 0x53, 0x5e, 0x30, 0x5e, 0xb7, 0x67, 0x9b, 0xc2,
                    0x3d, 0x76, 0x05, 0x93, 0x61, 0x85, 0xac, 0xe4, 0x69, 0x9e, 0x9a, 0x86, 0xe0,
                    0x31, 0xc5, 0x4b, 0x21, 0xcd, 0xd0, 0x96, 0x0a, 0xc1, 0x8d, 0xdd, 0x61, 0xec,
                    0x9f, 0x7a, 0xe9, 0x8d, 0x55, 0x82, 0xa6, 0xfa, 0xf6, 0x5f, 0x32, 0x48, 0xd1,
                ],
                fp: [
                    0x0b, 0xdc, 0x2d, 0x2b, 0x6e, 0xb1, 0xf9, 0x27, 0xcb, 0xab, 0xdb, 0xb9, 0xd4,
                    0x3d, 0xb8, 0xde, 0x85, 0x7b, 0xb7, 0x16, 0xdf, 0x86, 0xce, 0xcf, 0x08, 0x1e,
                    0x1a, 0x2b, 0x74, 0xfc, 0xad, 0x55,
                ],
                d0: None,
                d1: None,
                d2: None,
                dmax: None,
                internal_nsk: Some([
                    0x7b, 0x17, 0x17, 0x65, 0x27, 0xf9, 0x17, 0x99, 0x0f, 0x9f, 0x51, 0x79, 0xcb,
                    0x23, 0xc1, 0x6e, 0xc0, 0xa9, 0x26, 0xed, 0xc4, 0x1a, 0xb2, 0xba, 0x42, 0x13,
                    0x7b, 0xef, 0x5c, 0x20, 0x9f, 0x09,
                ]),
                internal_ovk: [
                    0xc6, 0x12, 0xcb, 0xc9, 0x77, 0x30, 0x7e, 0x53, 0x52, 0xa1, 0x58, 0x8b, 0xd7,
                    0x0f, 0x41, 0xaf, 0x11, 0xe7, 0x3b, 0x7b, 0xc6, 0xbc, 0xbc, 0x73, 0x2a, 0xa3,
                    0x06, 0xc2, 0x1c, 0xd0, 0x0f, 0x3a,
                ],
                internal_dk: [
                    0x35, 0xef, 0x4d, 0x26, 0x59, 0x51, 0xdc, 0xaa, 0xec, 0x26, 0xef, 0x8f, 0xbd,
                    0xf8, 0x4c, 0x92, 0xb7, 0x90, 0x04, 0x9d, 0x09, 0x93, 0x77, 0x2e, 0xfb, 0x43,
                    0x97, 0xf0, 0x49, 0x30, 0xf1, 0x67,
                ],
                internal_nk: [
                    0x8d, 0x05, 0x55, 0xe8, 0xe0, 0x20, 0xc9, 0xd3, 0x60, 0x68, 0x5d, 0x24, 0x2f,
                    0x2b, 0xa9, 0xf7, 0x74, 0x61, 0x3f, 0xa0, 0x94, 0x01, 0xf1, 0x25, 0xbc, 0xa9,
                    0x29, 0xec, 0xa4, 0x86, 0xa3, 0xd1,
                ],
                internal_ivk: [
                    0x7f, 0x7c, 0xee, 0xfa, 0x65, 0x42, 0x8e, 0x8b, 0x70, 0x76, 0x19, 0x1a, 0x23,
                    0x93, 0x95, 0x7b, 0x9c, 0x09, 0x50, 0x61, 0xd8, 0xcc, 0xe1, 0x28, 0x3d, 0xd1,
                    0x5c, 0x2b, 0x5e, 0x8f, 0xc3, 0x05,
                ],
                internal_xsk: Some([
                    0x02, 0x76, 0x84, 0x23, 0xcb, 0x02, 0x00, 0x00, 0x80, 0x44, 0x79, 0x08, 0x6c,
                    0x75, 0xd0, 0x80, 0x79, 0x60, 0x20, 0xf5, 0x00, 0xc1, 0xe3, 0x0a, 0x54, 0xcf,
                    0xe2, 0x9d, 0xda, 0x36, 0xf2, 0x14, 0x4f, 0xb3, 0x3a, 0x50, 0x80, 0x6f, 0xbe,
                    0xf7, 0xda, 0x7f, 0xf3, 0x5d, 0xb6, 0x9e, 0x13, 0xc3, 0x6f, 0x59, 0xad, 0x9c,
                    0x08, 0xd3, 0x2d, 0x52, 0x27, 0x37, 0x8d, 0xa0, 0xcf, 0xf9, 0x71, 0xfd, 0x42,
                    0x4b, 0xae, 0xf9, 0xa6, 0x33, 0x2f, 0x51, 0x06, 0x7b, 0x17, 0x17, 0x65, 0x27,
                    0xf9, 0x17, 0x99, 0x0f, 0x9f, 0x51, 0x79, 0xcb, 0x23, 0xc1, 0x6e, 0xc0, 0xa9,
                    0x26, 0xed, 0xc4, 0x1a, 0xb2, 0xba, 0x42, 0x13, 0x7b, 0xef, 0x5c, 0x20, 0x9f,
                    0x09, 0xc6, 0x12, 0xcb, 0xc9, 0x77, 0x30, 0x7e, 0x53, 0x52, 0xa1, 0x58, 0x8b,
                    0xd7, 0x0f, 0x41, 0xaf, 0x11, 0xe7, 0x3b, 0x7b, 0xc6, 0xbc, 0xbc, 0x73, 0x2a,
                    0xa3, 0x06, 0xc2, 0x1c, 0xd0, 0x0f, 0x3a, 0x35, 0xef, 0x4d, 0x26, 0x59, 0x51,
                    0xdc, 0xaa, 0xec, 0x26, 0xef, 0x8f, 0xbd, 0xf8, 0x4c, 0x92, 0xb7, 0x90, 0x04,
                    0x9d, 0x09, 0x93, 0x77, 0x2e, 0xfb, 0x43, 0x97, 0xf0, 0x49, 0x30, 0xf1, 0x67,
                ]),
                internal_xfvk: [
                    0x02, 0x76, 0x84, 0x23, 0xcb, 0x02, 0x00, 0x00, 0x80, 0x44, 0x79, 0x08, 0x6c,
                    0x75, 0xd0, 0x80, 0x79, 0x60, 0x20, 0xf5, 0x00, 0xc1, 0xe3, 0x0a, 0x54, 0xcf,
                    0xe2, 0x9d, 0xda, 0x36, 0xf2, 0x14, 0x4f, 0xb3, 0x3a, 0x50, 0x80, 0x6f, 0xbe,
                    0xf7, 0xda, 0x9a, 0x85, 0x3f, 0x95, 0x44, 0x71, 0x37, 0x97, 0xe0, 0x85, 0x17,
                    0x64, 0xda, 0x39, 0x2e, 0x68, 0x53, 0x4b, 0x1d, 0x94, 0x8d, 0xae, 0x47, 0x42,
                    0xee, 0x76, 0x5c, 0x72, 0x75, 0x72, 0xab, 0x4e, 0x8d, 0x05, 0x55, 0xe8, 0xe0,
                    0x20, 0xc9, 0xd3, 0x60, 0x68, 0x5d, 0x24, 0x2f, 0x2b, 0xa9, 0xf7, 0x74, 0x61,
                    0x3f, 0xa0, 0x94, 0x01, 0xf1, 0x25, 0xbc, 0xa9, 0x29, 0xec, 0xa4, 0x86, 0xa3,
                    0xd1, 0xc6, 0x12, 0xcb, 0xc9, 0x77, 0x30, 0x7e, 0x53, 0x52, 0xa1, 0x58, 0x8b,
                    0xd7, 0x0f, 0x41, 0xaf, 0x11, 0xe7, 0x3b, 0x7b, 0xc6, 0xbc, 0xbc, 0x73, 0x2a,
                    0xa3, 0x06, 0xc2, 0x1c, 0xd0, 0x0f, 0x3a, 0x35, 0xef, 0x4d, 0x26, 0x59, 0x51,
                    0xdc, 0xaa, 0xec, 0x26, 0xef, 0x8f, 0xbd, 0xf8, 0x4c, 0x92, 0xb7, 0x90, 0x04,
                    0x9d, 0x09, 0x93, 0x77, 0x2e, 0xfb, 0x43, 0x97, 0xf0, 0x49, 0x30, 0xf1, 0x67,
                ],
                internal_fp: [
                    0xe0, 0xba, 0xa5, 0xdb, 0xb8, 0x06, 0xc7, 0x21, 0x33, 0x3c, 0x63, 0x08, 0x34,
                    0x5f, 0xc5, 0x1c, 0x2d, 0xc1, 0xe0, 0x09, 0xda, 0x04, 0x47, 0x78, 0xa3, 0xc3,
                    0x29, 0x4d, 0x68, 0x17, 0xa3, 0xc4,
                ],
            },
            TestVector {
                ask: Some([
                    0x45, 0x93, 0xd2, 0x4d, 0x21, 0xe3, 0x59, 0x37, 0xf1, 0x52, 0xcf, 0x90, 0x46,
                    0x1c, 0x33, 0x2f, 0x69, 0x50, 0x3c, 0x10, 0x45, 0x81, 0xd6, 0x83, 0xe0, 0xac,
                    0x29, 0xf8, 0x4d, 0xec, 0xaf, 0x07,
                ]),
                nsk: Some([
                    0x1a, 0xc8, 0x7e, 0xc2, 0x12, 0x3f, 0x50, 0x57, 0xe3, 0xc0, 0xf8, 0x58, 0xe8,
                    0x0d, 0xfa, 0x0e, 0xe4, 0x55, 0x3d, 0xed, 0x27, 0xb7, 0xb5, 0xab, 0xfb, 0xb6,
                    0xfa, 0x6e, 0xff, 0xa7, 0xbb, 0x0b,
                ]),
                ovk: [
                    0x1e, 0x36, 0xea, 0x0c, 0xf2, 0xbe, 0x2e, 0x9d, 0x6c, 0xe3, 0x80, 0xa8, 0xaf,
                    0x18, 0xe7, 0x5d, 0xa9, 0x22, 0x55, 0x51, 0xfb, 0xef, 0x8b, 0x98, 0x31, 0x1b,
                    0x5c, 0x9c, 0x1b, 0x4b, 0x9e, 0xe3,
                ],
                dk: [
                    0x57, 0xfc, 0x6c, 0x59, 0xa4, 0xf3, 0xad, 0x5a, 0x6f, 0x60, 0x9d, 0xb6, 0x71,
                    0xd2, 0x8c, 0xbf, 0x70, 0x3f, 0x0d, 0x14, 0xdc, 0x36, 0x3a, 0xaa, 0xed, 0x70,
                    0x72, 0x9c, 0x10, 0x7b, 0xbb, 0x6a,
                ],
                c: [
                    0x33, 0xdc, 0x01, 0x2d, 0x76, 0x90, 0xce, 0xd2, 0xcd, 0x2b, 0xcb, 0x2c, 0xc3,
                    0xe4, 0x63, 0xe2, 0x8d, 0x8c, 0x29, 0xef, 0x3b, 0x01, 0xbe, 0x59, 0xb2, 0xbd,
                    0xfc, 0x38, 0x5b, 0xbd, 0xc7, 0x4b,
                ],
                ak: [
                    0x9c, 0x6d, 0x85, 0x9a, 0x75, 0x2c, 0x30, 0x5d, 0x62, 0x63, 0xde, 0x95, 0xf2,
                    0xfc, 0xf7, 0x34, 0xb1, 0x26, 0xdf, 0x24, 0x56, 0xc7, 0xd3, 0x1b, 0xc6, 0x01,
                    0xc8, 0xdd, 0xec, 0x40, 0x91, 0x12,
                ],
                nk: [
                    0xd3, 0xee, 0x41, 0xf8, 0x4b, 0x5a, 0x95, 0x08, 0xb6, 0x1d, 0x29, 0xb2, 0xfb,
                    0x45, 0x63, 0x6d, 0x19, 0xaa, 0x10, 0xd7, 0x82, 0xcd, 0x97, 0x8c, 0xfe, 0x67,
                    0x15, 0x49, 0x2f, 0xcd, 0x22, 0x4e,
                ],
                ivk: [
                    0xd1, 0x38, 0xe1, 0x37, 0xc6, 0x67, 0x1d, 0xe7, 0x82, 0xfb, 0x01, 0xba, 0x91,
                    0x1d, 0x98, 0x64, 0xbe, 0xbc, 0x44, 0x36, 0xcc, 0xb3, 0x88, 0xb4, 0xc1, 0xce,
                    0x02, 0x56, 0xa8, 0xdb, 0x74, 0x01,
                ],
                xsk: Some([
                    0x03, 0x0b, 0xdc, 0x2d, 0x2b, 0x03, 0x00, 0x00, 0x80, 0x33, 0xdc, 0x01, 0x2d,
                    0x76, 0x90, 0xce, 0xd2, 0xcd, 0x2b, 0xcb, 0x2c, 0xc3, 0xe4, 0x63, 0xe2, 0x8d,
                    0x8c, 0x29, 0xef, 0x3b, 0x01, 0xbe, 0x59, 0xb2, 0xbd, 0xfc, 0x38, 0x5b, 0xbd,
                    0xc7, 0x4b, 0x45, 0x93, 0xd2, 0x4d, 0x21, 0xe3, 0x59, 0x37, 0xf1, 0x52, 0xcf,
                    0x90, 0x46, 0x1c, 0x33, 0x2f, 0x69, 0x50, 0x3c, 0x10, 0x45, 0x81, 0xd6, 0x83,
                    0xe0, 0xac, 0x29, 0xf8, 0x4d, 0xec, 0xaf, 0x07, 0x1a, 0xc8, 0x7e, 0xc2, 0x12,
                    0x3f, 0x50, 0x57, 0xe3, 0xc0, 0xf8, 0x58, 0xe8, 0x0d, 0xfa, 0x0e, 0xe4, 0x55,
                    0x3d, 0xed, 0x27, 0xb7, 0xb5, 0xab, 0xfb, 0xb6, 0xfa, 0x6e, 0xff, 0xa7, 0xbb,
                    0x0b, 0x1e, 0x36, 0xea, 0x0c, 0xf2, 0xbe, 0x2e, 0x9d, 0x6c, 0xe3, 0x80, 0xa8,
                    0xaf, 0x18, 0xe7, 0x5d, 0xa9, 0x22, 0x55, 0x51, 0xfb, 0xef, 0x8b, 0x98, 0x31,
                    0x1b, 0x5c, 0x9c, 0x1b, 0x4b, 0x9e, 0xe3, 0x57, 0xfc, 0x6c, 0x59, 0xa4, 0xf3,
                    0xad, 0x5a, 0x6f, 0x60, 0x9d, 0xb6, 0x71, 0xd2, 0x8c, 0xbf, 0x70, 0x3f, 0x0d,
                    0x14, 0xdc, 0x36, 0x3a, 0xaa, 0xed, 0x70, 0x72, 0x9c, 0x10, 0x7b, 0xbb, 0x6a,
                ]),
                xfvk: [
                    0x03, 0x0b, 0xdc, 0x2d, 0x2b, 0x03, 0x00, 0x00, 0x80, 0x33, 0xdc, 0x01, 0x2d,
                    0x76, 0x90, 0xce, 0xd2, 0xcd, 0x2b, 0xcb, 0x2c, 0xc3, 0xe4, 0x63, 0xe2, 0x8d,
                    0x8c, 0x29, 0xef, 0x3b, 0x01, 0xbe, 0x59, 0xb2, 0xbd, 0xfc, 0x38, 0x5b, 0xbd,
                    0xc7, 0x4b, 0x9c, 0x6d, 0x85, 0x9a, 0x75, 0x2c, 0x30, 0x5d, 0x62, 0x63, 0xde,
                    0x95, 0xf2, 0xfc, 0xf7, 0x34, 0xb1, 0x26, 0xdf, 0x24, 0x56, 0xc7, 0xd3, 0x1b,
                    0xc6, 0x01, 0xc8, 0xdd, 0xec, 0x40, 0x91, 0x12, 0xd3, 0xee, 0x41, 0xf8, 0x4b,
                    0x5a, 0x95, 0x08, 0xb6, 0x1d, 0x29, 0xb2, 0xfb, 0x45, 0x63, 0x6d, 0x19, 0xaa,
                    0x10, 0xd7, 0x82, 0xcd, 0x97, 0x8c, 0xfe, 0x67, 0x15, 0x49, 0x2f, 0xcd, 0x22,
                    0x4e, 0x1e, 0x36, 0xea, 0x0c, 0xf2, 0xbe, 0x2e, 0x9d, 0x6c, 0xe3, 0x80, 0xa8,
                    0xaf, 0x18, 0xe7, 0x5d, 0xa9, 0x22, 0x55, 0x51, 0xfb, 0xef, 0x8b, 0x98, 0x31,
                    0x1b, 0x5c, 0x9c, 0x1b, 0x4b, 0x9e, 0xe3, 0x57, 0xfc, 0x6c, 0x59, 0xa4, 0xf3,
                    0xad, 0x5a, 0x6f, 0x60, 0x9d, 0xb6, 0x71, 0xd2, 0x8c, 0xbf, 0x70, 0x3f, 0x0d,
                    0x14, 0xdc, 0x36, 0x3a, 0xaa, 0xed, 0x70, 0x72, 0x9c, 0x10, 0x7b, 0xbb, 0x6a,
                ],
                fp: [
                    0xdf, 0x0a, 0x89, 0xbd, 0x88, 0x35, 0x39, 0xc0, 0x7b, 0x89, 0xe0, 0x4c, 0x92,
                    0x76, 0x4e, 0xc2, 0xd1, 0x59, 0x69, 0x0f, 0x5a, 0xd5, 0xdd, 0x3d, 0x0a, 0xd8,
                    0xac, 0x29, 0x69, 0xde, 0x22, 0xc8,
                ],
                d0: None,
                d1: None,
                d2: None,
                dmax: Some([
                    0xb8, 0x31, 0xc2, 0x96, 0x5a, 0x86, 0x0a, 0xd7, 0x60, 0xec, 0x2a,
                ]),
                internal_nsk: Some([
                    0x9c, 0x39, 0x3c, 0x5b, 0xd7, 0x66, 0x4d, 0x63, 0xef, 0xa1, 0xba, 0xea, 0x99,
                    0xfc, 0x6d, 0xc4, 0x74, 0xfe, 0xa7, 0x53, 0xce, 0x84, 0xc8, 0x81, 0xd9, 0xef,
                    0x28, 0x77, 0x86, 0x75, 0xb1, 0x05,
                ]),
                internal_ovk: [
                    0x69, 0xaa, 0xb0, 0x2e, 0xa6, 0x43, 0x57, 0x9d, 0x4d, 0x85, 0x2a, 0xf8, 0xb4,
                    0x32, 0xb8, 0x8d, 0x1c, 0xa0, 0x00, 0x44, 0x4a, 0xb0, 0x73, 0x7a, 0x41, 0x15,
                    0xe0, 0x63, 0xf1, 0x48, 0xd2, 0x72,
                ],
                internal_dk: [
                    0x88, 0x26, 0xa9, 0x3c, 0x65, 0xc6, 0x6e, 0x75, 0x54, 0x32, 0x74, 0xe6, 0x72,
                    0xad, 0xf5, 0x59, 0xf7, 0xd7, 0x26, 0x5e, 0x99, 0xcc, 0x11, 0xda, 0x4a, 0x14,
                    0x20, 0xa3, 0x7b, 0x92, 0xf7, 0xab,
                ],
                internal_nk: [
                    0x59, 0xba, 0xa9, 0x0f, 0x83, 0x4a, 0x66, 0x1b, 0xf2, 0xbe, 0x42, 0x46, 0xa4,
                    0x3d, 0x18, 0x9c, 0x7d, 0x0e, 0x17, 0xa8, 0x24, 0x7b, 0x4f, 0xd9, 0xd2, 0xe1,
                    0x53, 0xa5, 0x97, 0x3d, 0xc8, 0xec,
                ],
                internal_ivk: [
                    0x8a, 0x86, 0xfb, 0x27, 0x81, 0xfe, 0x6f, 0x24, 0xd9, 0x60, 0xdd, 0xdb, 0x2f,
                    0x78, 0x13, 0xc0, 0x31, 0xfe, 0xc5, 0x5d, 0x26, 0xcc, 0xde, 0xe1, 0xf7, 0x18,
                    0x2a, 0x3e, 0xc6, 0x83, 0xcf, 0x04,
                ],
                internal_xsk: Some([
                    0x03, 0x0b, 0xdc, 0x2d, 0x2b, 0x03, 0x00, 0x00, 0x80, 0x33, 0xdc, 0x01, 0x2d,
                    0x76, 0x90, 0xce, 0xd2, 0xcd, 0x2b, 0xcb, 0x2c, 0xc3, 0xe4, 0x63, 0xe2, 0x8d,
                    0x8c, 0x29, 0xef, 0x3b, 0x01, 0xbe, 0x59, 0xb2, 0xbd, 0xfc, 0x38, 0x5b, 0xbd,
                    0xc7, 0x4b, 0x45, 0x93, 0xd2, 0x4d, 0x21, 0xe3, 0x59, 0x37, 0xf1, 0x52, 0xcf,
                    0x90, 0x46, 0x1c, 0x33, 0x2f, 0x69, 0x50, 0x3c, 0x10, 0x45, 0x81, 0xd6, 0x83,
                    0xe0, 0xac, 0x29, 0xf8, 0x4d, 0xec, 0xaf, 0x07, 0x9c, 0x39, 0x3c, 0x5b, 0xd7,
                    0x66, 0x4d, 0x63, 0xef, 0xa1, 0xba, 0xea, 0x99, 0xfc, 0x6d, 0xc4, 0x74, 0xfe,
                    0xa7, 0x53, 0xce, 0x84, 0xc8, 0x81, 0xd9, 0xef, 0x28, 0x77, 0x86, 0x75, 0xb1,
                    0x05, 0x69, 0xaa, 0xb0, 0x2e, 0xa6, 0x43, 0x57, 0x9d, 0x4d, 0x85, 0x2a, 0xf8,
                    0xb4, 0x32, 0xb8, 0x8d, 0x1c, 0xa0, 0x00, 0x44, 0x4a, 0xb0, 0x73, 0x7a, 0x41,
                    0x15, 0xe0, 0x63, 0xf1, 0x48, 0xd2, 0x72, 0x88, 0x26, 0xa9, 0x3c, 0x65, 0xc6,
                    0x6e, 0x75, 0x54, 0x32, 0x74, 0xe6, 0x72, 0xad, 0xf5, 0x59, 0xf7, 0xd7, 0x26,
                    0x5e, 0x99, 0xcc, 0x11, 0xda, 0x4a, 0x14, 0x20, 0xa3, 0x7b, 0x92, 0xf7, 0xab,
                ]),
                internal_xfvk: [
                    0x03, 0x0b, 0xdc, 0x2d, 0x2b, 0x03, 0x00, 0x00, 0x80, 0x33, 0xdc, 0x01, 0x2d,
                    0x76, 0x90, 0xce, 0xd2, 0xcd, 0x2b, 0xcb, 0x2c, 0xc3, 0xe4, 0x63, 0xe2, 0x8d,
                    0x8c, 0x29, 0xef, 0x3b, 0x01, 0xbe, 0x59, 0xb2, 0xbd, 0xfc, 0x38, 0x5b, 0xbd,
                    0xc7, 0x4b, 0x9c, 0x6d, 0x85, 0x9a, 0x75, 0x2c, 0x30, 0x5d, 0x62, 0x63, 0xde,
                    0x95, 0xf2, 0xfc, 0xf7, 0x34, 0xb1, 0x26, 0xdf, 0x24, 0x56, 0xc7, 0xd3, 0x1b,
                    0xc6, 0x01, 0xc8, 0xdd, 0xec, 0x40, 0x91, 0x12, 0x59, 0xba, 0xa9, 0x0f, 0x83,
                    0x4a, 0x66, 0x1b, 0xf2, 0xbe, 0x42, 0x46, 0xa4, 0x3d, 0x18, 0x9c, 0x7d, 0x0e,
                    0x17, 0xa8, 0x24, 0x7b, 0x4f, 0xd9, 0xd2, 0xe1, 0x53, 0xa5, 0x97, 0x3d, 0xc8,
                    0xec, 0x69, 0xaa, 0xb0, 0x2e, 0xa6, 0x43, 0x57, 0x9d, 0x4d, 0x85, 0x2a, 0xf8,
                    0xb4, 0x32, 0xb8, 0x8d, 0x1c, 0xa0, 0x00, 0x44, 0x4a, 0xb0, 0x73, 0x7a, 0x41,
                    0x15, 0xe0, 0x63, 0xf1, 0x48, 0xd2, 0x72, 0x88, 0x26, 0xa9, 0x3c, 0x65, 0xc6,
                    0x6e, 0x75, 0x54, 0x32, 0x74, 0xe6, 0x72, 0xad, 0xf5, 0x59, 0xf7, 0xd7, 0x26,
                    0x5e, 0x99, 0xcc, 0x11, 0xda, 0x4a, 0x14, 0x20, 0xa3, 0x7b, 0x92, 0xf7, 0xab,
                ],
                internal_fp: [
                    0x3f, 0x63, 0x16, 0x1d, 0x5b, 0x43, 0x72, 0x04, 0xf7, 0x01, 0x2a, 0x3a, 0x1d,
                    0x36, 0x58, 0x1d, 0xab, 0x39, 0x7a, 0x84, 0x3b, 0x2c, 0x58, 0x98, 0x11, 0xed,
                    0xcc, 0x5b, 0x50, 0x1c, 0xd4, 0xeb,
                ],
            },
        ];

        let seed = [
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
            24, 25, 26, 27, 28, 29, 30, 31,
        ];

        let i1h = ChildIndex::hardened(1);
        let i2h = ChildIndex::hardened(2);
        let i3h = ChildIndex::hardened(3);

        let m = ExtendedSpendingKey::master(&seed);
        let m_1h = m.derive_child(i1h);
        let m_1h_2h = ExtendedSpendingKey::from_path(&m, &[i1h, i2h]);
        let m_1h_2h_3h = m_1h_2h.derive_child(i3h);

        let xfvks = [
            m.to_extended_full_viewing_key(),
            m_1h.to_extended_full_viewing_key(),
            m_1h_2h.to_extended_full_viewing_key(),
            m_1h_2h_3h.to_extended_full_viewing_key(),
        ];
        assert_eq!(test_vectors.len(), xfvks.len());

        let xsks = [m, m_1h, m_1h_2h, m_1h_2h_3h];

        for (xsk, tv) in xsks.iter().zip(test_vectors.iter()) {
            assert_eq!(xsk.expsk.ask.to_bytes(), tv.ask.unwrap());
            assert_eq!(xsk.expsk.nsk.to_repr().as_ref(), tv.nsk.unwrap());

            assert_eq!(xsk.expsk.ovk.0, tv.ovk);
            assert_eq!(xsk.dk.0, tv.dk);
            assert_eq!(xsk.chain_code.as_bytes(), &tv.c);

            let mut ser = vec![];
            xsk.write(&mut ser).unwrap();
            assert_eq!(&ser[..], &tv.xsk.unwrap()[..]);

            let internal_xsk = xsk.derive_internal();
            assert_eq!(internal_xsk.expsk.ask.to_bytes(), tv.ask.unwrap());
            assert_eq!(
                internal_xsk.expsk.nsk.to_repr().as_ref(),
                tv.internal_nsk.unwrap()
            );

            assert_eq!(internal_xsk.expsk.ovk.0, tv.internal_ovk);
            assert_eq!(internal_xsk.dk.0, tv.internal_dk);
            assert_eq!(internal_xsk.chain_code.as_bytes(), &tv.c);

            let mut ser = vec![];
            internal_xsk.write(&mut ser).unwrap();
            assert_eq!(&ser[..], &tv.internal_xsk.unwrap()[..]);
        }

        for (xfvk, tv) in xfvks.iter().zip(test_vectors.iter()) {
            assert_eq!(xfvk.fvk.vk.ak.to_bytes(), tv.ak);
            assert_eq!(xfvk.fvk.vk.nk.0.to_bytes(), tv.nk);

            assert_eq!(xfvk.fvk.ovk.0, tv.ovk);
            assert_eq!(xfvk.dk.0, tv.dk);
            assert_eq!(xfvk.chain_code.as_bytes(), &tv.c);

            assert_eq!(xfvk.fvk.vk.ivk().to_repr().as_ref(), tv.ivk);

            let mut ser = vec![];
            xfvk.write(&mut ser).unwrap();
            assert_eq!(&ser[..], &tv.xfvk[..]);
            assert_eq!(FvkFingerprint::from(&xfvk.fvk).0, tv.fp);

            // d0
            let mut di = DiversifierIndex::new();
            match xfvk.dk.find_diversifier(di).unwrap() {
                (l, d) if l == di => assert_eq!(d.0, tv.d0.unwrap()),
                (_, _) => assert!(tv.d0.is_none()),
            }

            // d1
            di.increment().unwrap();
            match xfvk.dk.find_diversifier(di).unwrap() {
                (l, d) if l == di => assert_eq!(d.0, tv.d1.unwrap()),
                (_, _) => assert!(tv.d1.is_none()),
            }

            // d2
            di.increment().unwrap();
            match xfvk.dk.find_diversifier(di).unwrap() {
                (l, d) if l == di => assert_eq!(d.0, tv.d2.unwrap()),
                (_, _) => assert!(tv.d2.is_none()),
            }

            // dmax
            let dmax = DiversifierIndex::from([0xff; 11]);
            match xfvk.dk.find_diversifier(dmax) {
                Some((l, d)) if l == dmax => assert_eq!(d.0, tv.dmax.unwrap()),
                Some((_, _)) => panic!(),
                None => assert!(tv.dmax.is_none()),
            }

            let internal_xfvk = xfvk.derive_internal();
            assert_eq!(internal_xfvk.fvk.vk.ak.to_bytes(), tv.ak);
            assert_eq!(internal_xfvk.fvk.vk.nk.0.to_bytes(), tv.internal_nk);

            assert_eq!(internal_xfvk.fvk.ovk.0, tv.internal_ovk);
            assert_eq!(internal_xfvk.dk.0, tv.internal_dk);
            assert_eq!(internal_xfvk.chain_code.as_bytes(), &tv.c);

            assert_eq!(
                internal_xfvk.fvk.vk.ivk().to_repr().as_ref(),
                tv.internal_ivk
            );

            let mut ser = vec![];
            internal_xfvk.write(&mut ser).unwrap();
            assert_eq!(&ser[..], &tv.internal_xfvk[..]);
            assert_eq!(FvkFingerprint::from(&internal_xfvk.fvk).0, tv.internal_fp);

            let dfvk = xfvk.to_diversifiable_full_viewing_key();
            let ivk = dfvk.to_external_ivk();
            let ivk_bytes = ivk.to_bytes();
            assert_eq!(&ivk_bytes[..32], tv.dk);
            assert_eq!(&ivk_bytes[32..], tv.ivk);

            let ivk_rt = IncomingViewingKey::from_bytes(&ivk_bytes).unwrap();
            assert_eq!(ivk.dk, ivk_rt.dk);
            assert_eq!(ivk.ivk.0, ivk_rt.ivk.0);
        }
    }
}

#[cfg(any(test, feature = "test-dependencies"))]
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
pub mod testing {
    use proptest::collection::vec;
    use proptest::prelude::{any, prop_compose};

    use super::ExtendedSpendingKey;

    prop_compose! {
        pub fn arb_extended_spending_key()(v in vec(any::<u8>(), 32..252)) -> ExtendedSpendingKey {
            ExtendedSpendingKey::master(&v)
        }
    }
}
