//! Implementation of [ZIP 32] for hierarchical deterministic key management.
//!
//! [ZIP 32]: https://zips.z.cash/zip-0032

use aes::Aes256;
use blake2b_simd::Params as Blake2bParams;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
use fpe::ff1::{BinaryNumeralString, FF1};
use pairing::bls12_381::Bls12;
use std::ops::AddAssign;

use crate::{
    jubjub::{fs::Fs, FixedGenerators, JubjubEngine, JubjubParams, ToUniform},
    primitives::{Diversifier, PaymentAddress, ViewingKey},
};
use std::io::{self, Read, Write};

use crate::{
    keys::{prf_expand, prf_expand_vec, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey},
    JUBJUB,
};

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

// Common helper functions

fn derive_child_ovk(parent: &OutgoingViewingKey, i_l: &[u8]) -> OutgoingViewingKey {
    let mut ovk = [0u8; 32];
    ovk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x15], &parent.0]).as_bytes()[..32]);
    OutgoingViewingKey(ovk)
}

// ZIP 32 structures

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

impl<E: JubjubEngine> From<&FullViewingKey<E>> for FVKFingerprint {
    fn from(fvk: &FullViewingKey<E>) -> 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)
    }
}

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

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

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

/// A child index for a derived key
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ChildIndex {
    NonHardened(u32),
    Hardened(u32), // Hardened(n) == n + (1 << 31) == n' in path notation
}

impl ChildIndex {
    pub fn from_index(i: u32) -> Self {
        match i {
            n if n >= (1 << 31) => ChildIndex::Hardened(n - (1 << 31)),
            n => ChildIndex::NonHardened(n),
        }
    }

    fn master() -> Self {
        ChildIndex::from_index(0)
    }

    fn to_index(&self) -> u32 {
        match *self {
            ChildIndex::Hardened(i) => i + (1 << 31),
            ChildIndex::NonHardened(i) => i,
        }
    }
}

/// A chain code
#[derive(Clone, Copy, Debug, PartialEq)]
struct ChainCode([u8; 32]);

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DiversifierIndex(pub [u8; 11]);

impl DiversifierIndex {
    pub fn new() -> Self {
        DiversifierIndex([0; 11])
    }

    pub fn increment(&mut self) -> Result<(), ()> {
        for k in 0..11 {
            self.0[k] = self.0[k].wrapping_add(1);
            if self.0[k] != 0 {
                // No overflow
                return Ok(());
            }
        }
        // Overflow
        Err(())
    }
}

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

impl DiversifierKey {
    pub fn master(sk_m: &[u8]) -> Self {
        let mut dk_m = [0u8; 32];
        dk_m.copy_from_slice(&prf_expand(sk_m, &[0x10]).as_bytes()[..32]);
        DiversifierKey(dk_m)
    }

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

    /// Returns the first index starting from j that generates a valid
    /// diversifier, along with the corresponding diversifier. Returns
    /// an error if the diversifier space is exhausted.
    pub fn diversifier(
        &self,
        mut j: DiversifierIndex,
    ) -> Result<(DiversifierIndex, Diversifier), ()> {
        let ff = FF1::<Aes256>::new(&self.0, 2).unwrap();
        loop {
            // Generate d_j
            let enc = ff
                .encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.0[..]))
                .unwrap();
            let mut d_j = [0; 11];
            d_j.copy_from_slice(&enc.to_bytes_le());
            let d_j = Diversifier(d_j);

            // Return (j, d_j) if valid, else increment j and try again
            match d_j.g_d::<Bls12>(&JUBJUB) {
                Some(_) => return Ok((j, d_j)),
                None => {
                    if j.increment().is_err() {
                        return Err(());
                    }
                }
            }
        }
    }
}

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

// A Sapling extended full viewing key
#[derive(Clone)]
pub struct ExtendedFullViewingKey {
    depth: u8,
    parent_fvk_tag: FVKTag,
    child_index: ChildIndex,
    chain_code: ChainCode,
    pub fvk: FullViewingKey<Bls12>,
    dk: DiversifierKey,
}

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

impl std::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 std::fmt::Debug for ExtendedFullViewingKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        write!(
            f,
            "ExtendedFullViewingKey(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: ChildIndex::master(),
            chain_code: ChainCode(c_m),
            expsk: ExpandedSpendingKey::from_spending_key(sk_m),
            dk: DiversifierKey::master(sk_m),
        }
    }

    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
        let depth = reader.read_u8()?;
        let mut tag = [0; 4];
        reader.read_exact(&mut tag)?;
        let i = reader.read_u32::<LittleEndian>()?;
        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: ChildIndex::from_index(i),
            chain_code: ChainCode(c),
            expsk,
            dk: DiversifierKey(dk),
        })
    }

    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
        writer.write_u8(self.depth)?;
        writer.write_all(&self.parent_fvk_tag.0)?;
        writer.write_u32::<LittleEndian>(self.child_index.to_index())?;
        writer.write_all(&self.chain_code.0)?;
        writer.write_all(&self.expsk.to_bytes())?;
        writer.write_all(&self.dk.0)?;

        Ok(())
    }

    /// 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
    }

    pub fn derive_child(&self, i: ChildIndex) -> Self {
        let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk, &JUBJUB);
        let tmp = match i {
            ChildIndex::Hardened(i) => {
                let mut le_i = [0; 4];
                LittleEndian::write_u32(&mut le_i, i + (1 << 31));
                prf_expand_vec(
                    &self.chain_code.0,
                    &[&[0x11], &self.expsk.to_bytes(), &self.dk.0, &le_i],
                )
            }
            ChildIndex::NonHardened(i) => {
                let mut le_i = [0; 4];
                LittleEndian::write_u32(&mut le_i, i);
                prf_expand_vec(
                    &self.chain_code.0,
                    &[&[0x12], &fvk.to_bytes(), &self.dk.0, &le_i],
                )
            }
        };
        let i_l = &tmp.as_bytes()[..32];
        let mut c_i = [0u8; 32];
        c_i.copy_from_slice(&tmp.as_bytes()[32..]);

        ExtendedSpendingKey {
            depth: self.depth + 1,
            parent_fvk_tag: FVKFingerprint::from(&fvk).tag(),
            child_index: i,
            chain_code: ChainCode(c_i),
            expsk: {
                let mut ask = Fs::to_uniform(prf_expand(i_l, &[0x13]).as_bytes());
                let mut nsk = Fs::to_uniform(prf_expand(i_l, &[0x14]).as_bytes());
                ask.add_assign(&self.expsk.ask);
                nsk.add_assign(&self.expsk.nsk);
                let ovk = derive_child_ovk(&self.expsk.ovk, i_l);
                ExpandedSpendingKey { ask, nsk, ovk }
            },
            dk: self.dk.derive_child(i_l),
        }
    }

    pub fn default_address(&self) -> Result<(DiversifierIndex, PaymentAddress<Bls12>), ()> {
        ExtendedFullViewingKey::from(self).default_address()
    }
}

impl<'a> From<&'a ExtendedSpendingKey> for ExtendedFullViewingKey {
    fn from(xsk: &ExtendedSpendingKey) -> Self {
        ExtendedFullViewingKey {
            depth: xsk.depth,
            parent_fvk_tag: xsk.parent_fvk_tag,
            child_index: xsk.child_index,
            chain_code: xsk.chain_code,
            fvk: FullViewingKey::from_expanded_spending_key(&xsk.expsk, &JUBJUB),
            dk: xsk.dk,
        }
    }
}

impl ExtendedFullViewingKey {
    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
        let depth = reader.read_u8()?;
        let mut tag = [0; 4];
        reader.read_exact(&mut tag)?;
        let i = reader.read_u32::<LittleEndian>()?;
        let mut c = [0; 32];
        reader.read_exact(&mut c)?;
        let fvk = FullViewingKey::read(&mut reader, &*JUBJUB)?;
        let mut dk = [0; 32];
        reader.read_exact(&mut dk)?;

        Ok(ExtendedFullViewingKey {
            depth,
            parent_fvk_tag: FVKTag(tag),
            child_index: ChildIndex::from_index(i),
            chain_code: ChainCode(c),
            fvk,
            dk: DiversifierKey(dk),
        })
    }

    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
        writer.write_u8(self.depth)?;
        writer.write_all(&self.parent_fvk_tag.0)?;
        writer.write_u32::<LittleEndian>(self.child_index.to_index())?;
        writer.write_all(&self.chain_code.0)?;
        writer.write_all(&self.fvk.to_bytes())?;
        writer.write_all(&self.dk.0)?;

        Ok(())
    }

    pub fn derive_child(&self, i: ChildIndex) -> Result<Self, ()> {
        let tmp = match i {
            ChildIndex::Hardened(_) => return Err(()),
            ChildIndex::NonHardened(i) => {
                let mut le_i = [0; 4];
                LittleEndian::write_u32(&mut le_i, i);
                prf_expand_vec(
                    &self.chain_code.0,
                    &[&[0x12], &self.fvk.to_bytes(), &self.dk.0, &le_i],
                )
            }
        };
        let i_l = &tmp.as_bytes()[..32];
        let mut c_i = [0u8; 32];
        c_i.copy_from_slice(&tmp.as_bytes()[32..]);

        Ok(ExtendedFullViewingKey {
            depth: self.depth + 1,
            parent_fvk_tag: FVKFingerprint::from(&self.fvk).tag(),
            child_index: i,
            chain_code: ChainCode(c_i),
            fvk: {
                let i_ask = Fs::to_uniform(prf_expand(i_l, &[0x13]).as_bytes());
                let i_nsk = Fs::to_uniform(prf_expand(i_l, &[0x14]).as_bytes());
                let ak = JUBJUB
                    .generator(FixedGenerators::SpendingKeyGenerator)
                    .mul(i_ask, &JUBJUB)
                    .add(&self.fvk.vk.ak, &JUBJUB);
                let nk = JUBJUB
                    .generator(FixedGenerators::ProofGenerationKey)
                    .mul(i_nsk, &JUBJUB)
                    .add(&self.fvk.vk.nk, &JUBJUB);

                FullViewingKey {
                    vk: ViewingKey { ak, nk },
                    ovk: derive_child_ovk(&self.fvk.ovk, i_l),
                }
            },
            dk: self.dk.derive_child(i_l),
        })
    }

    pub fn address(
        &self,
        j: DiversifierIndex,
    ) -> Result<(DiversifierIndex, PaymentAddress<Bls12>), ()> {
        let (j, d_j) = match self.dk.diversifier(j) {
            Ok(ret) => ret,
            Err(()) => return Err(()),
        };
        match self.fvk.vk.to_payment_address(d_j, &JUBJUB) {
            Some(addr) => Ok((j, addr)),
            None => Err(()),
        }
    }

    pub fn default_address(&self) -> Result<(DiversifierIndex, PaymentAddress<Bls12>), ()> {
        self.address(DiversifierIndex::new())
    }
}

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

    use ff::{PrimeField, PrimeFieldRepr};

    #[test]
    fn derive_nonhardened_child() {
        let seed = [0; 32];
        let xsk_m = ExtendedSpendingKey::master(&seed);
        let xfvk_m = ExtendedFullViewingKey::from(&xsk_m);

        let i_5 = ChildIndex::NonHardened(5);
        let xsk_5 = xsk_m.derive_child(i_5);
        let xfvk_5 = xfvk_m.derive_child(i_5);

        assert!(xfvk_5.is_ok());
        assert_eq!(ExtendedFullViewingKey::from(&xsk_5), xfvk_5.unwrap());
    }

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

        let i_5h = ChildIndex::Hardened(5);
        let xsk_5h = xsk_m.derive_child(i_5h);
        let xfvk_5h = xfvk_m.derive_child(i_5h);

        // Cannot derive a hardened child from an ExtendedFullViewingKey
        assert!(xfvk_5h.is_err());
        let xfvk_5h = ExtendedFullViewingKey::from(&xsk_5h);

        let i_7 = ChildIndex::NonHardened(7);
        let xsk_5h_7 = xsk_5h.derive_child(i_7);
        let xfvk_5h_7 = xfvk_5h.derive_child(i_7);

        // But we *can* derive a non-hardened child from a hardened parent
        assert!(xfvk_5h_7.is_ok());
        assert_eq!(ExtendedFullViewingKey::from(&xsk_5h_7), xfvk_5h_7.unwrap());
    }

    #[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::NonHardened(7));
        assert_eq!(
            ExtendedSpendingKey::from_path(
                &xsk_m,
                &[ChildIndex::Hardened(5), ChildIndex::NonHardened(7)]
            ),
            xsk_5h_7
        );
    }

    #[test]
    fn diversifier() {
        let dk = DiversifierKey([0; 32]);
        let j_0 = DiversifierIndex::new();
        let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        let j_3 = DiversifierIndex([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.diversifier(j_0).unwrap();
        assert_eq!(j, j_0);
        assert_eq!(d_j.0, d_0);

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

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

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

    #[test]
    fn default_address() {
        let seed = [0; 32];
        let xsk_m = ExtendedSpendingKey::master(&seed);
        let (j_m, addr_m) = xsk_m.default_address().unwrap();
        assert_eq!(j_m.0, [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]
    fn read_write() {
        let seed = [0; 32];
        let xsk = ExtendedSpendingKey::master(&seed);
        let fvk = ExtendedFullViewingKey::from(&xsk);

        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]
    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]>,
        };

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

        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 i1 = ChildIndex::NonHardened(1);
        let i2h = ChildIndex::Hardened(2);
        let i3 = ChildIndex::NonHardened(3);

        let m = ExtendedSpendingKey::master(&seed);
        let m_1 = m.derive_child(i1);
        let m_1_2h = ExtendedSpendingKey::from_path(&m, &[i1, i2h]);
        let m_1_2hv = ExtendedFullViewingKey::from(&m_1_2h);
        let m_1_2hv_3 = m_1_2hv.derive_child(i3).unwrap();

        let xfvks = [
            ExtendedFullViewingKey::from(&m),
            ExtendedFullViewingKey::from(&m_1),
            ExtendedFullViewingKey::from(&m_1_2h),
            m_1_2hv, // Appears twice so we can de-duplicate test code below
            m_1_2hv_3,
        ];
        assert_eq!(test_vectors.len(), xfvks.len());

        let xsks = [m, m_1, m_1_2h];

        for j in 0..xsks.len() {
            let xsk = &xsks[j];
            let tv = &test_vectors[j];

            let mut buf = [0; 32];
            xsk.expsk.ask.into_repr().write_le(&mut buf[..]).unwrap();
            assert_eq!(buf, tv.ask.unwrap());
            xsk.expsk.nsk.into_repr().write_le(&mut buf[..]).unwrap();
            assert_eq!(buf, tv.nsk.unwrap());

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

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

        for j in 0..xfvks.len() {
            let xfvk = &xfvks[j];
            let tv = &test_vectors[j];

            let mut buf = [0; 32];
            xfvk.fvk.vk.ak.write(&mut buf[..]).unwrap();
            assert_eq!(buf, tv.ak);
            xfvk.fvk.vk.nk.write(&mut buf[..]).unwrap();
            assert_eq!(buf, tv.nk);

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

            xfvk.fvk
                .vk
                .ivk()
                .into_repr()
                .write_le(&mut buf[..])
                .unwrap();
            assert_eq!(buf, 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.diversifier(di) {
                Ok((l, d)) if l == di => assert_eq!(d.0, tv.d0.unwrap()),
                Ok((_, _)) => assert!(tv.d0.is_none()),
                Err(_) => panic!(),
            }

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

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

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