diff --git a/CHANGELOG.md b/CHANGELOG.md index 8394e51..0a5ec3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.1.0] + +### Fixed + +- Producing nullifiers from decoding with viewing keys + +## [0.8.0.0] + +### Added + +- `UnifiedIncomingViewingKey` type +- `ValidVk` type +- Address derivation from full viewing keys +- Address derivation from incoming viewing keys +- Orchard note decoding with full viewing key +- Orchard note decoding with incoming viewing key +- Sapling note decoding with full viewing key +- Sapling note decoding with incoming viewing key + +### Fixed + +- Transparent viewing key component generation + ## [0.7.8.1] ### Changed diff --git a/cabal.project.freeze b/cabal.project.freeze index 206c446..821fe1c 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -3,13 +3,13 @@ constraints: any.Cabal ==3.10.3.0, any.Cabal-syntax ==3.10.3.0, any.HUnit ==1.6.2.0, any.OneTuple ==0.4.2, - any.QuickCheck ==2.14.3, + any.QuickCheck ==2.15.0.1, QuickCheck -old-random +templatehaskell, any.StateVar ==1.2.2, any.aeson ==2.2.3.0, aeson +ordered-keymap, - any.alex ==3.5.1.0, - any.ansi-terminal ==1.1.1, + any.alex ==3.5.2.0, + any.ansi-terminal ==1.1.2, ansi-terminal -example, any.ansi-terminal-types ==1.1, any.appar ==0.1.8, @@ -25,7 +25,7 @@ constraints: any.Cabal ==3.10.3.0, attoparsec -developer, any.attoparsec-aeson ==2.2.2.0, any.base ==4.18.2.1, - any.base-orphans ==0.9.2, + any.base-orphans ==0.9.3, any.base16 ==1.0, any.base16-bytestring ==1.0.2.0, any.base58-bytestring ==0.1.0, @@ -40,7 +40,7 @@ constraints: any.Cabal ==3.10.3.0, any.blaze-builder ==0.4.2.3, any.borsh ==0.3.0, any.byteorder ==1.0.4, - any.bytes ==0.17.3, + any.bytes ==0.17.4, any.bytestring ==0.11.5.3, any.c2hs ==0.28.8, c2hs +base3 -regression, @@ -52,28 +52,25 @@ constraints: any.Cabal ==3.10.3.0, cereal -bytestring-builder, any.character-ps ==0.1, any.colour ==2.3.6, - any.comonad ==5.0.8, + any.comonad ==5.0.9, comonad +containers +distributive +indexed-traversable, any.conduit ==1.3.6, - any.conduit-extra ==1.3.6, + any.conduit-extra ==1.3.7, any.containers ==0.6.7, any.contravariant ==1.5.5, contravariant +semigroups +statevar +tagged, any.cookie ==0.5.0, - any.crypton ==1.0.0, + any.crypton ==1.0.1, crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes, - any.crypton-connection ==0.4.1, + any.crypton-connection ==0.4.3, any.crypton-x509 ==1.7.7, any.crypton-x509-store ==1.6.9, any.crypton-x509-system ==1.6.7, - any.crypton-x509-validation ==1.6.12, + any.crypton-x509-validation ==1.6.13, any.cryptonite ==0.30, cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes, - any.data-default ==0.7.1.1, - any.data-default-class ==0.1.2.0, - any.data-default-instances-containers ==0.0.1, - any.data-default-instances-dlist ==0.0.1, - any.data-default-instances-old-locale ==0.0.1, + any.data-default ==0.8.0.0, + any.data-default-class ==0.2.0.0, any.data-fix ==0.3.4, any.deepseq ==1.4.8.1, any.directory ==1.3.8.4, @@ -81,9 +78,9 @@ constraints: any.Cabal ==3.10.3.0, distributive +semigroups +tagged, any.dlist ==1.0, dlist -werror, - any.entropy ==0.4.1.10, + any.entropy ==0.4.1.11, entropy -donotgetentropy, - any.envy ==2.1.3.0, + any.envy ==2.1.4.0, any.exceptions ==0.10.7, any.filepath ==1.4.300.1, any.foreign-rust ==0.1.0, @@ -92,56 +89,61 @@ constraints: any.Cabal ==3.10.3.0, any.ghc-bignum ==1.3, any.ghc-boot-th ==9.6.5, any.ghc-prim ==0.10.0, - any.half ==0.3.1, - any.happy ==2.0.2, - any.happy-lib ==2.0.2, - any.hashable ==1.4.7.0, - hashable -arch-native +integer-gmp -random-initial-seed, - any.haskell-lexer ==1.1.1, + any.half ==0.3.2, + any.happy ==2.1.4, + any.happy-lib ==2.1.4, + any.hashable ==1.5.0.0, + hashable -arch-native -random-initial-seed, + any.haskell-lexer ==1.1.2, any.haskoin-core ==1.1.0, any.hexstring ==0.12.1.0, any.hourglass ==0.2.12, any.hsc2hs ==0.68.10, hsc2hs -in-ghc-tree, - any.hspec ==2.11.9, - any.hspec-core ==2.11.9, - any.hspec-discover ==2.11.9, + any.hspec ==2.11.10, + any.hspec-core ==2.11.10, + any.hspec-discover ==2.11.10, any.hspec-expectations ==0.8.4, - any.http-client ==0.7.17, + any.http-client ==0.7.18, http-client +network-uri, - any.http-client-tls ==0.3.6.3, - any.http-conduit ==2.3.9, + any.http-client-tls ==0.3.6.4, + any.http-conduit ==2.3.9.1, http-conduit +aeson, any.http-types ==0.12.4, any.indexed-traversable ==0.1.4, any.indexed-traversable-instances ==0.1.2, any.integer-conversion ==0.1.1, any.integer-gmp ==1.1, - any.integer-logarithms ==1.0.3.1, + any.integer-logarithms ==1.0.4, integer-logarithms -check-bounds +integer-gmp, - any.iproute ==1.7.14, - any.language-c ==0.9.3, - language-c -allwarnings +iecfpextension +usebytestrings, + any.iproute ==1.7.15, + any.language-c ==0.10.0, + language-c +iecfpextension +usebytestrings, any.memory ==0.18.0, memory +support_bytestring +support_deepseq, any.mime-types ==0.1.2.0, - any.mono-traversable ==1.0.20.0, + any.mono-traversable ==1.0.21.0, any.mtl ==2.3.1, any.murmur3 ==1.0.5, - any.network ==3.2.4.0, + any.network ==3.2.7.0, network -devel, any.network-uri ==2.6.4.2, any.old-locale ==1.0.0.7, any.old-time ==1.1.0.4, - any.os-string ==2.0.6, + any.optparse-applicative ==0.18.1.0, + optparse-applicative +process, + any.os-string ==2.0.7, any.parsec ==3.1.16.1, any.pem ==0.2.4, any.pretty ==1.1.3.6, + any.prettyprinter ==1.7.1, + prettyprinter -buildreadme +text, + any.prettyprinter-ansi-terminal ==1.1.3, any.primitive ==0.9.0.0, any.process ==1.6.19.0, any.quickcheck-io ==0.2.0, any.quickcheck-transformer ==0.3.1.2, - any.random ==1.2.1.2, + any.random ==1.2.1.3, any.regex-base ==0.94.0.2, any.regex-compat ==0.95.2.1, any.regex-posix ==0.96.0.1, @@ -151,7 +153,7 @@ constraints: any.Cabal ==3.10.3.0, any.safe ==0.3.21, any.scientific ==0.3.8.0, scientific -integer-simple, - any.secp256k1-haskell ==1.4.0, + any.secp256k1-haskell ==1.4.2, any.semialign ==1.3.1, semialign +semigroupoids, any.semigroupoids ==6.0.1, @@ -161,42 +163,44 @@ constraints: any.Cabal ==3.10.3.0, any.socks ==0.6.1, any.sop-core ==0.5.0.2, any.split ==0.2.5, - any.splitmix ==0.1.0.5, + any.splitmix ==0.1.1, splitmix -optimised-mixer, any.stm ==2.5.1.0, - any.streaming-commons ==0.2.2.6, + any.streaming-commons ==0.2.3.0, streaming-commons -use-bytestring-builder, any.strict ==0.5.1, any.string-conversions ==0.4.0.1, - any.tagged ==0.8.8, + any.tagged ==0.8.9, tagged +deepseq +transformers, + any.tasty ==1.5.3, + tasty +unix, any.template-haskell ==2.20.0.0, any.text ==2.0.2, any.text-iso8601 ==0.1.1, any.text-short ==0.1.6, text-short -asserts, any.tf-random ==0.5, - any.th-abstraction ==0.7.0.0, - any.th-compat ==0.1.5, + any.th-abstraction ==0.7.1.0, + any.th-compat ==0.1.6, any.these ==1.2.1, any.time ==1.12.2, - any.time-compat ==1.9.7, - any.tls ==2.1.0, + any.time-compat ==1.9.8, + any.tls ==2.1.7, tls -devel, any.transformers ==0.6.1.0, any.transformers-compat ==0.7.2, transformers-compat -five +five-three -four +generic-deriving +mtl -three -two, any.typed-process ==0.2.12.0, any.unix ==2.8.4.0, - any.unix-time ==0.4.15, + any.unix-time ==0.4.16, any.unliftio-core ==0.2.1.0, any.unordered-containers ==0.2.20, unordered-containers -debug, any.utf8-string ==1.0.2, any.uuid-types ==1.0.6, - any.vector ==0.13.1.0, + any.vector ==0.13.2.0, vector +boundschecks -internalchecks -unsafechecks -wall, - any.vector-algorithms ==0.9.0.2, + any.vector-algorithms ==0.9.0.3, vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks, any.vector-stream ==0.1.0.1, any.void ==0.7.3, @@ -205,4 +209,4 @@ constraints: any.Cabal ==3.10.3.0, any.witherable ==0.5, any.zlib ==0.7.1.0, zlib -bundled-c-zlib +non-blocking-ffi +pkg-config -index-state: hackage.haskell.org 2024-10-11T12:55:31Z +index-state: hackage.haskell.org 2025-01-31T18:30:19Z diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 025b49b..f1160b5 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -66,7 +66,8 @@ use sapling_crypto::{ }, note_encryption::{ SaplingDomain, - Zip212Enforcement + Zip212Enforcement, + try_sapling_note_decryption }, bundle::{ GrothProofBytes, @@ -82,7 +83,8 @@ use sapling_crypto::{ }, zip32::{ sapling_find_address, - DiversifierKey + DiversifierKey, + IncomingViewingKey as SaplingIncomingViewingKey } }; @@ -157,7 +159,7 @@ use orchard::{ Flags }, Action, - keys::{SpendAuthorizingKey, SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope}, + keys::{SpendAuthorizingKey, SpendingKey, FullViewingKey, IncomingViewingKey, PreparedIncomingViewingKey, Scope}, note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, note_encryption::OrchardDomain, primitives::redpallas::{VerificationKey, SpendAuth, Signature}, @@ -1143,6 +1145,116 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_note_decrypt_fvk( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + scope: bool, + pos: u64, + out: *mut u8, + out_len: &mut usize + ){ + let dfvk: Vec = marshall_from_haskell_var(key, key_len, RW); + let mut note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW); + let svk = DiversifiableFullViewingKey::from_bytes(&to_array(dfvk)); + if svk.is_some().into() { + let domain = SaplingDomain::new(Zip212Enforcement::On); + let action2 = note_input.to_output_description(); + match action2 { + Ok(action3) => { + let nk = + if scope { + svk.clone().unwrap().to_nk(SaplingScope::External) + } else { + svk.clone().unwrap().to_nk(SaplingScope::Internal) + }; + let fvk = + if scope { + svk.unwrap().to_ivk(SaplingScope::External) + } else { + svk.unwrap().to_ivk(SaplingScope::Internal) + }; + let pivk = SaplingPreparedIncomingViewingKey::new(&fvk); + let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action3); + match result { + Some((n, r, m)) => { + let rseed = match n.rseed() { + Rseed::BeforeZip212(x) => { + Hrseed { kind: 1, bytes: x.to_bytes().to_vec()} + }, + Rseed::AfterZip212(y) => { + Hrseed { kind: 2, bytes: y.to_vec()} + } + }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: n.nf(&nk, pos).to_vec(), rho: vec![0], rseed}; + marshall_to_haskell_var(&hn, out, out_len, RW); + } + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + }, + Err(_e1) => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] , nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + } else { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_note_decrypt_ivk( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let ivk: Vec = marshall_from_haskell_var(key, key_len, RW); + let mut note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW); + let svk = SaplingIncomingViewingKey::from_bytes(&to_array(ivk)); + if svk.is_some().into() { + let action2 = note_input.to_output_description(); + match action2 { + Ok(action3) => { + let result = try_sapling_note_decryption(&svk.unwrap().prepare(), &action3, Zip212Enforcement::On); + match result { + Some((n, r, m)) => { + let rseed = match n.rseed() { + Rseed::BeforeZip212(x) => { + Hrseed { kind: 1, bytes: x.to_bytes().to_vec()} + }, + Rseed::AfterZip212(y) => { + Hrseed { kind: 2, bytes: y.to_vec()} + } + }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: vec![0], rho: vec![0], rseed}; + marshall_to_haskell_var(&hn, out, out_len, RW); + } + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + }, + Err(_e1) => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] , nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + } else { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_orchard_note_decrypt( key: *const u8, @@ -1189,6 +1301,99 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_orchard_note_decrypt_fvk( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + external: bool, + out: *mut u8, + out_len: &mut usize + ){ + let fvk_input: Vec = marshall_from_haskell_var(key, key_len, RW); + let note_input: Haction = marshall_from_haskell_var(note, note_len, RW); + let action: Action> = Action::from_parts( + Nullifier::from_bytes(&to_array(note_input.nf.bytes)).unwrap(), + VerificationKey::try_from(to_array(note_input.rk.bytes)).unwrap(), + ExtractedNoteCommitment::from_bytes(&to_array(note_input.cmx.bytes)).unwrap(), + TransmittedNoteCiphertext {epk_bytes: to_array(note_input.eph_key.bytes), enc_ciphertext: to_array(note_input.enc_txt.bytes), out_ciphertext: to_array(note_input.out_txt.bytes)}, + ValueCommitment::from_bytes(&to_array(note_input.cv.bytes)).unwrap(), + Signature::from(to_array(note_input.auth.bytes))); + let fvk_array = to_array(fvk_input); + let domain = OrchardDomain::for_action(&action); + let dec_fvk = FullViewingKey::from_bytes(&fvk_array); + match dec_fvk { + Some(fvk) => { + let ivk = if external { + fvk.to_ivk(Scope::External) + } else { + fvk.to_ivk(Scope::Internal) + }; + let pivk = PreparedIncomingViewingKey::new(&ivk); + let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); + match result { + Some((n, r, m)) => { + let rho = n.rho().to_bytes().to_vec(); + let rseed = Hrseed {kind: 3, bytes: n.rseed().as_bytes().to_vec()}; + let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: n.nullifier(&fvk).to_bytes().to_vec(), rho, rseed}; + marshall_to_haskell_var(&hn, out, out_len, RW); + } + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + }, + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_orchard_note_decrypt_ivk( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let ivk_input: Vec = marshall_from_haskell_var(key, key_len, RW); + let note_input: Haction = marshall_from_haskell_var(note, note_len, RW); + let action: Action> = Action::from_parts( + Nullifier::from_bytes(&to_array(note_input.nf.bytes)).unwrap(), + VerificationKey::try_from(to_array(note_input.rk.bytes)).unwrap(), + ExtractedNoteCommitment::from_bytes(&to_array(note_input.cmx.bytes)).unwrap(), + TransmittedNoteCiphertext {epk_bytes: to_array(note_input.eph_key.bytes), enc_ciphertext: to_array(note_input.enc_txt.bytes), out_ciphertext: to_array(note_input.out_txt.bytes)}, + ValueCommitment::from_bytes(&to_array(note_input.cv.bytes)).unwrap(), + Signature::from(to_array(note_input.auth.bytes))); + let ivk_array = to_array(ivk_input); + let domain = OrchardDomain::for_action(&action); + let dec_fvk = IncomingViewingKey::from_bytes(&ivk_array); + if dec_fvk.is_some().into() { + let pivk = PreparedIncomingViewingKey::new(&dec_fvk.unwrap()); + let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); + match result { + Some((n, r, m)) => { + let rho = n.rho().to_bytes().to_vec(); + let rseed = Hrseed {kind: 3, bytes: n.rseed().as_bytes().to_vec()}; + let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: vec![0], rho, rseed}; + marshall_to_haskell_var(&hn, out, out_len, RW); + } + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + } else { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_orchard_note_decrypt_sk( key: *const u8, @@ -1370,29 +1575,20 @@ pub extern "C" fn rust_wrapper_sapling_paymentaddress( out_len: &mut usize ){ let extspk: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); - if div_ix == 0 { - let sp_key = ExtendedSpendingKey::from_bytes(&extspk); - match sp_key { - Ok(sp_key_x) => { - let (_def_div, def_address) = sp_key_x.default_address(); - marshall_to_haskell_var(&def_address.to_bytes().to_vec(), out, out_len, RW); - }, - Err(_e) => { - marshall_to_haskell_var(&vec![0], out, out_len, RW); - } - } - } else { - let expsk = ExpandedSpendingKey::from_spending_key(&extspk); - let fvk = SaplingFullViewingKey::from_expanded_spending_key(&expsk); - let dk = DiversifierKey::master(&extspk); - let result = sapling_find_address(&fvk, &dk, DiversifierIndex::from(div_ix)); - match result { - Some((_d, p_address)) => { - marshall_to_haskell_var(&p_address.to_bytes().to_vec(), out, out_len, RW); - }, - None => { - marshall_to_haskell_var(&vec![0], out, out_len, RW); - } + let sp_key = ExtendedSpendingKey::from_bytes(&extspk); + match sp_key { + Ok(sp_key_x) => { + let (_def_div, def_address) = + if div_ix == 0 { + sp_key_x.default_address() + } else { + let dfvk = sp_key_x.to_diversifiable_full_viewing_key(); + dfvk.find_address(DiversifierIndex::from(div_ix)).unwrap() + }; + marshall_to_haskell_var(&def_address.to_bytes().to_vec(), out, out_len, RW); + }, + Err(_e) => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); } } } @@ -1420,6 +1616,91 @@ pub extern "C" fn rust_wrapper_sapling_chgpaymentaddress( marshall_to_haskell_var(&cPmtAddress.to_bytes().to_vec(), out, out_len, RW); } +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_receiver_fvk( + fvk_in: *const u8, + fvk_in_len: usize, + div_ix: u32, + out: *mut u8, + out_len: &mut usize + ){ + let fvk_bytes: Vec = marshall_from_haskell_var(fvk_in, fvk_in_len, RW); + let fvk = DiversifiableFullViewingKey::from_bytes(&to_array(fvk_bytes)); + if div_ix == 0 { + match fvk { + Some(k) => { + let (_def_div, def_address) = k.default_address(); + marshall_to_haskell_var(&def_address.to_bytes().to_vec(), out, out_len, RW); + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } + } else { + match fvk { + Some(k) => { + let result = k.find_address(DiversifierIndex::from(div_ix)); + match result { + Some((_d, p_address)) => { + marshall_to_haskell_var(&p_address.to_bytes().to_vec(), out, out_len, RW); + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_change_receiver_fvk( + fvk_in: *const u8, + fvk_in_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let fvk_bytes: Vec = marshall_from_haskell_var(fvk_in, fvk_in_len, RW); + let dfvk = DiversifiableFullViewingKey::from_bytes(&to_array(fvk_bytes)); + match dfvk { + Some(k) => { + let ( _div_ix, chg_address ) = k.change_address(); + marshall_to_haskell_var(&chg_address.to_bytes().to_vec(), out, out_len, RW); + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_receiver_ivk( + ivk_in: *const u8, + ivk_in_len: usize, + div_ix: u32, + out: *mut u8, + out_len: &mut usize + ){ + let ivk_bytes: Vec = marshall_from_haskell_var(ivk_in, ivk_in_len, RW); + let ivk = SaplingIncomingViewingKey::from_bytes(&to_array(ivk_bytes)); + if ivk.is_some().into() { + let result = ivk.unwrap().find_address(DiversifierIndex::from(div_ix)); + match result { + Some((_d, p_address)) => { + marshall_to_haskell_var(&p_address.to_bytes().to_vec(), out, out_len, RW); + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } + } else { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_derive_orchard_spending_key( seed: *const u8, @@ -1461,6 +1742,49 @@ pub extern "C" fn rust_wrapper_derive_orchard_receiver( } +#[no_mangle] +pub extern "C" fn rust_wrapper_derive_orchard_receiver_fvk( + f_key: *const u8, + f_key_len: usize, + add_id: u32, + scope: bool, + out: *mut u8, + out_len: &mut usize + ){ + let vk_in: Vec = marshall_from_haskell_var(f_key, f_key_len, RW); + let fvk = FullViewingKey::from_bytes(&to_array(vk_in)); + let sc = if scope { + Scope::External + } else {Scope::Internal}; + match fvk { + Some(k) => { + let o_rec = k.address_at(add_id, sc); + marshall_to_haskell_var(&o_rec.to_raw_address_bytes().to_vec(), out, out_len, RW); + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_derive_orchard_receiver_ivk( + f_key: *const u8, + f_key_len: usize, + add_id: u32, + out: *mut u8, + out_len: &mut usize + ){ + let vk_in: Vec = marshall_from_haskell_var(f_key, f_key_len, RW); + let ivk = IncomingViewingKey::from_bytes(&to_array(vk_in)); + if ivk.is_some().into() { + let o_rec = ivk.unwrap().address_at(add_id); + marshall_to_haskell_var(&o_rec.to_raw_address_bytes().to_vec(), out, out_len, RW); + } else { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_bech32_encode( hr: *const u8, diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index b4c26de..4d59e65 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -135,6 +135,23 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_orchard_note_decrypt_fvk as rustWrapperOrchardNoteDecodeFvk + { toBorshVar* `BS.ByteString'& + , toBorshVar* `OrchardAction'& + , `Bool' + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_orchard_note_decrypt_ivk as rustWrapperOrchardNoteDecodeIvk + { toBorshVar* `BS.ByteString'& + , toBorshVar* `OrchardAction'& + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_orchard_note_decrypt_sk as rustWrapperOrchardNoteDecodeSK { toBorshVar* `BS.ByteString'& , toBorshVar* `OrchardAction'& @@ -210,6 +227,23 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_derive_orchard_receiver_fvk as rustWrapperGenOrchardReceiverFvk + { toBorshVar* `BS.ByteString'& + , `Word32' + , `Bool' + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_derive_orchard_receiver_ivk as rustWrapperGenOrchardReceiverIvk + { toBorshVar* `BS.ByteString'& + , `Word32' + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_read_sapling_commitment_tree as rustWrapperReadSaplingCommitmentTree { toBorshVar* `SaplingFrontier'& , toBorshVar* `BS.ByteString'& @@ -445,3 +479,44 @@ import ZcashHaskell.Types } -> `()' #} + +{# fun unsafe rust_wrapper_sapling_receiver_fvk as rustWrapperSaplingReceiverFvk + { toBorshVar* `BS.ByteString'& + , `Word32' + , getVarBuffer `Buffer BS.ByteString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_receiver_ivk as rustWrapperSaplingReceiverIvk + { toBorshVar* `BS.ByteString'& + , `Word32' + , getVarBuffer `Buffer BS.ByteString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_change_receiver_fvk as rustWrapperSaplingChgReceiverFvk + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer BS.ByteString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_note_decrypt_fvk as rustWrapperSaplingDecodeFvk + { toBorshVar* `BS.ByteString'& + , toBorshVar* `ShieldedOutput'& + , `Bool' + , `Int64' + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_note_decrypt_ivk as rustWrapperSaplingDecodeIvk + { toBorshVar* `BS.ByteString'& + , toBorshVar* `ShieldedOutput'& + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} diff --git a/src/ZcashHaskell/Keys.hs b/src/ZcashHaskell/Keys.hs index 67de27b..1ebecbf 100644 --- a/src/ZcashHaskell/Keys.hs +++ b/src/ZcashHaskell/Keys.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} + -- Copyright 2022-2024 Vergara Technologies LLC -- This file is part of Zcash-Haskell. -- @@ -15,9 +17,11 @@ module ZcashHaskell.Keys where import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed) -import Crypto.Secp256k1 (createContext) +import Crypto.Secp256k1 (PubKey(..), createContext, exportPubKey) +import qualified Data.Binary as Bi import qualified Data.ByteString as BS -import Data.HexString (hexBytes) +import qualified Data.ByteString.Lazy as BSL +import Data.HexString (HexString(..), hexBytes) import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word (Word8(..)) @@ -44,6 +48,9 @@ import ZcashHaskell.Types , Seed(..) , ToBytes(..) , TransparentSpendingKey(..) + , UnifiedFullViewingKey(..) + , UnifiedIncomingViewingKey(..) + , ValidVk(..) , ZcashNet(..) , uniFullViewingKeyHrp , uniIncomingViewingKeyHrp @@ -70,11 +77,11 @@ getWalletSeed p = deriveFullTransparentNode :: TransparentSpendingKey -> IO BS.ByteString deriveFullTransparentNode sk = do ioCtx <- createContext - let tPubKey = deriveXPubKey ioCtx sk - let tPubKeyBytes = decodeBase58 $ xPubExport btc ioCtx tPubKey - case tPubKeyBytes of - Nothing -> fail "Unable to get transparent key bytes" - Just pb -> return $ BS.takeEnd 65 pb + let (XPubKey d p i c k) = deriveXPubKey ioCtx sk + let tPubKeyBytes = BSL.toStrict (Bi.encode c) <> exportPubKey ioCtx True k + if BS.length tPubKeyBytes == 65 + then return tPubKeyBytes + else fail "Unable to get transparent key bytes" -- | Derive a transparent incoming root node for unified incoming viewing keys deriveIncomingTransparentNode :: TransparentSpendingKey -> IO BS.ByteString @@ -82,72 +89,113 @@ deriveIncomingTransparentNode sk = do ioCtx <- createContext let path = Deriv :/ 0 :: DerivPath let childPrvKey = derivePath ioCtx path sk - let tPubKey = deriveXPubKey ioCtx childPrvKey - let tPubKeyBytes = decodeBase58 $ xPubExport btc ioCtx tPubKey - case tPubKeyBytes of - Nothing -> fail "Unable to get transparent key bytes" - Just pb -> return $ BS.takeEnd 65 pb + let (XPubKey d p i c k) = deriveXPubKey ioCtx childPrvKey + let tPubKeyBytes = BSL.toStrict (Bi.encode c) <> exportPubKey ioCtx True k + if BS.length tPubKeyBytes == 65 + then return tPubKeyBytes + else fail "Unable to get transparent key bytes" -- | Derive a Unified Full Viewing Key deriveUfvk :: ZcashNet - -> OrchardSpendingKey - -> SaplingSpendingKey - -> TransparentSpendingKey - -> IO T.Text + -> Maybe OrchardSpendingKey + -> Maybe SaplingSpendingKey + -> Maybe TransparentSpendingKey + -> IO ValidVk deriveUfvk net okey skey tkey = do - tSec <- deriveFullTransparentNode tkey - let oSec = deriveOrchardFvk okey - let sSec = deriveSaplingFvk skey + tSec <- maybe (return BS.empty) deriveFullTransparentNode tkey + let oSec = deriveOrchardFvk =<< okey + let sSec = deriveSaplingFvk =<< skey case oSec of Nothing -> fail "Unable to derive Orchard viewing key" Just oSec' -> do - case sSec of - Nothing -> fail "Unable to derive Sapling viewing key" - Just sSec' -> - return $ encodeVK (hexBytes oSec') (hexBytes sSec') tSec net True + return $ + FullVk $ + UnifiedFullViewingKey + (case net of + MainNet -> 1 + TestNet -> 2) + (hexBytes oSec') + (maybe BS.empty hexBytes sSec) + tSec -- | Derive a Unified Incoming Viewing Key deriveUivk :: ZcashNet - -> OrchardSpendingKey - -> SaplingSpendingKey - -> TransparentSpendingKey - -> IO T.Text + -> Maybe OrchardSpendingKey + -> Maybe SaplingSpendingKey + -> Maybe TransparentSpendingKey + -> IO ValidVk deriveUivk net okey skey tkey = do - tSec <- deriveIncomingTransparentNode tkey - let oSec = deriveOrchardIvk okey - let sSec = deriveSaplingIvk skey + tSec <- maybe (return BS.empty) deriveIncomingTransparentNode tkey + let oSec = deriveOrchardIvk =<< okey + let sSec = deriveSaplingIvk =<< skey case oSec of Nothing -> fail "Unable to derive Orchard viewing key" Just oSec' -> do - case sSec of - Nothing -> fail "Unable to derive Sapling viewing key" - Just sSec' -> - return $ encodeVK (hexBytes oSec') (hexBytes sSec') tSec net False + return $ + IncomingVk $ + UnifiedIncomingViewingKey + (case net of + MainNet -> 1 + TestNet -> 2) + (hexBytes oSec') + (maybe BS.empty hexBytes sSec) + tSec -- | Encode a Unified Viewing Key per [ZIP-316](https://zips.z.cash/zip-0316) encodeVK :: - BS.ByteString -- ^ Orchard FVK - -> BS.ByteString -- ^ Sapling FVK - -> BS.ByteString -- ^ Transparent root node - -> ZcashNet -- ^ Network - -> Bool -- ^ Full? + ValidVk -- ^ The viewing key -> T.Text -encodeVK ovk svk tvk net full = encodeBech32m (E.encodeUtf8 hr) b +encodeVK vk = encodeBech32m (E.encodeUtf8 hr) b where - tReceiver = packReceiver 0x00 $ Just tvk + tReceiver = + if tvk /= BS.empty + then packReceiver 0x00 $ Just tvk + else packReceiver 0x00 Nothing b = f4Jumble $ tReceiver <> sReceiver <> oReceiver <> padding + tvk = + case vk of + FullVk f -> t_key f + IncomingVk i -> i_t_key i + svk = + case vk of + FullVk f -> s_key f + IncomingVk i -> i_s_key i + ovk = + case vk of + FullVk f -> o_key f + IncomingVk i -> i_o_key i + znet = + case vk of + FullVk f -> + if net f == 1 + then MainNet + else TestNet + IncomingVk i -> + if i_net i == 1 + then MainNet + else TestNet hr = - if full - then case net of - MainNet -> uniFullViewingKeyHrp - TestNet -> uniTestFullViewingKeyHrp - else case net of - MainNet -> uniIncomingViewingKeyHrp - TestNet -> uniTestIncomingViewingKeyHrp - sReceiver = packReceiver 0x02 $ Just svk - oReceiver = packReceiver 0x03 $ Just ovk + case vk of + FullVk _ -> + case znet of + MainNet -> uniFullViewingKeyHrp + TestNet -> uniTestFullViewingKeyHrp + IncomingVk _ -> + case znet of + MainNet -> uniIncomingViewingKeyHrp + TestNet -> uniTestIncomingViewingKeyHrp + sReceiver = + packReceiver 0x02 $ + if svk /= BS.empty + then Just svk + else Nothing + oReceiver = + packReceiver 0x03 $ + if ovk /= BS.empty + then Just ovk + else Nothing padding = E.encodeUtf8 $ T.justifyLeft 16 '\NUL' hr packReceiver :: Word8 -> Maybe BS.ByteString -> BS.ByteString packReceiver typeCode receiver' = diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index b63a914..7e7799a 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -22,11 +22,15 @@ import C.Zcash , rustWrapperCreateOrchardFvk , rustWrapperCreateOrchardIvk , rustWrapperGenOrchardReceiver + , rustWrapperGenOrchardReceiverFvk + , rustWrapperGenOrchardReceiverIvk , rustWrapperGenOrchardSpendKey , rustWrapperGetOrchardRootTest , rustWrapperOrchardAddNodeTest , rustWrapperOrchardCheck , rustWrapperOrchardNoteDecode + , rustWrapperOrchardNoteDecodeFvk + , rustWrapperOrchardNoteDecodeIvk , rustWrapperOrchardNoteDecodeSK , rustWrapperReadOrchardCommitmentTree , rustWrapperReadOrchardFrontier @@ -93,6 +97,35 @@ genOrchardReceiver i scope osk = (fromIntegral i) (scope == External) +-- | Derives an Orchard receiver for the given full viewing key and index +genOrchardReceiverFvk :: + Int -- ^ The index of the address to be created + -> Scope -- ^ `External` for wallet addresses, `Internal` for change addresses + -> BS.ByteString -- ^ The full viewing key + -> Maybe OrchardReceiver +genOrchardReceiverFvk i scope osk = + if BS.length k /= 43 + then Nothing + else Just $ OrchardReceiver k + where + k = + withPureBorshVarBuffer $ + rustWrapperGenOrchardReceiverFvk osk (fromIntegral i) (scope == External) + +-- | Derives an Orchard receiver for the given incoming viewing key and index +genOrchardReceiverIvk :: + Int -- ^ The index of the address to be created + -> BS.ByteString -- ^ The full viewing key + -> Maybe OrchardReceiver +genOrchardReceiverIvk i osk = + if BS.length k /= 43 + then Nothing + else Just $ OrchardReceiver k + where + k = + withPureBorshVarBuffer $ + rustWrapperGenOrchardReceiverIvk osk (fromIntegral i) + -- | Checks if given bytestring is a valid encoded unified address isValidUnifiedAddress :: BS.ByteString -> Maybe UnifiedAddress isValidUnifiedAddress str = @@ -185,6 +218,30 @@ decryptOrchardAction key encAction = withPureBorshVarBuffer $ rustWrapperOrchardNoteDecode (o_key key) encAction +-- | Attempts to decode the given @OrchardAction@ using the given @UnifiedFullViewingKey@. +decryptOrchardActionFvk :: + UnifiedFullViewingKey -> Scope -> OrchardAction -> Maybe DecodedNote +decryptOrchardActionFvk key scope encAction = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = + withPureBorshVarBuffer $ + rustWrapperOrchardNoteDecodeFvk (o_key key) encAction (scope == External) + +-- | Attempts to decode the given @OrchardAction@ using the given @UnifiedFullViewingKey@. +decryptOrchardActionIvk :: + UnifiedIncomingViewingKey -> OrchardAction -> Maybe DecodedNote +decryptOrchardActionIvk key encAction = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = + withPureBorshVarBuffer $ + rustWrapperOrchardNoteDecodeIvk (i_o_key key) encAction + getSaplingFromUA :: BS.ByteString -> Maybe T.Text getSaplingFromUA uadd = do let a = isValidUnifiedAddress uadd diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 7b5cf72..bae6668 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -34,9 +34,14 @@ import C.Zcash , rustWrapperReadSaplingWitness , rustWrapperSaplingCheck , rustWrapperSaplingChgPaymentAddress + , rustWrapperSaplingChgReceiverFvk , rustWrapperSaplingDecodeEsk + , rustWrapperSaplingDecodeFvk + , rustWrapperSaplingDecodeIvk , rustWrapperSaplingNoteDecode , rustWrapperSaplingPaymentAddress + , rustWrapperSaplingReceiverFvk + , rustWrapperSaplingReceiverIvk , rustWrapperSaplingSpendingkey , rustWrapperSaplingVkDecode , rustWrapperTxParse @@ -46,7 +51,7 @@ import Data.Aeson import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C import Data.HexString (HexString(..), fromText, hexString, toBytes, toText) -import Data.Int (Int8) +import Data.Int (Int64, Int8) import qualified Data.Text as T import Data.Word import Foreign.Rust.Marshall.Variable @@ -95,6 +100,27 @@ decodeSaplingOutput key out = decodedAction = withPureBorshVarBuffer $ rustWrapperSaplingNoteDecode key out +-- | Attempt to decode the given @ShieldedOutput@ using the given Sapling full viewing key +decodeSaplingOutputFvk :: + BS.ByteString -> ShieldedOutput -> Scope -> Int64 -> Maybe DecodedNote +decodeSaplingOutputFvk fvk so scope pos = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = + withPureBorshVarBuffer $ + rustWrapperSaplingDecodeFvk fvk so (scope == External) pos + +-- | Attempt to decode the given @ShieldedOutput@ using the given Sapling incoming viewing key +decodeSaplingOutputIvk :: BS.ByteString -> ShieldedOutput -> Maybe DecodedNote +decodeSaplingOutputIvk fvk so = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = withPureBorshVarBuffer $ rustWrapperSaplingDecodeIvk fvk so + instance FromJSON RawTxResponse where parseJSON = withObject "RawTxResponse" $ \obj -> do @@ -184,6 +210,28 @@ genSaplingPaymentAddress i extspk = (getBytes extspk) (fromIntegral (i * 111))) +-- | Attempts to generate a Sapling receiver using an full viewing key and a diversifier index +genSaplingReceiverFvk :: Int -> BS.ByteString -> Maybe SaplingReceiver +genSaplingReceiverFvk i fvk = + if BS.length res == 43 + then Just $ SaplingReceiver res + else Nothing + where + res = + withPureBorshVarBuffer + (rustWrapperSaplingReceiverFvk fvk (fromIntegral (i * 111))) + +-- | Attempts to generate a Sapling receiver using an incoming viewing key and a diversifier index +genSaplingReceiverIvk :: Int -> BS.ByteString -> Maybe SaplingReceiver +genSaplingReceiverIvk i ivk = + if BS.length res == 43 + then Just $ SaplingReceiver res + else Nothing + where + res = + withPureBorshVarBuffer + (rustWrapperSaplingReceiverIvk ivk (fromIntegral (i * 111))) + -- | Generate an internal Sapling address genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingReceiver genSaplingInternalAddress sk = @@ -194,6 +242,15 @@ genSaplingInternalAddress sk = res = withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk) +-- | Generate a change Sapling receiver from Full Viewing Key +genSaplingInternalAddressFvk :: BS.ByteString -> Maybe SaplingReceiver +genSaplingInternalAddressFvk fvk = + if BS.length res == 43 + then Just $ SaplingReceiver res + else Nothing + where + res = withPureBorshVarBuffer (rustWrapperSaplingChgReceiverFvk fvk) + getSaplingNodeValue :: BS.ByteString -> Maybe HexString getSaplingNodeValue cmu = if BS.length (hexBytes n) > 1 diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 287fe99..9e30786 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -20,33 +20,36 @@ module ZcashHaskell.Transparent where import Control.Exception (throwIO) import Crypto.Hash import Crypto.Secp256k1 +import qualified Data.Binary as Bi import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import Data.ByteString.Base58 (bitcoinAlphabet, decodeBase58, encodeBase58) -import qualified Data.ByteString.Char8 as BC +import qualified Data.ByteString.Lazy as BSL +import qualified Data.ByteString.Short as Short import Data.Char (chr) import Data.HexString import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word import Haskoin.Address (Address(..)) -import qualified Haskoin.Crypto.Hash as H +import qualified Haskoin.Crypto.Hash as H (Hash256(..)) import Haskoin.Crypto.Keys.Extended import ZcashHaskell.Types - --- ( AccountId --- , CoinType(..) --- , Scope(..) --- , Seed(..) --- , ToBytes(..) --- , TransparentAddress(..) --- , TransparentReceiver(..) --- , TransparentSpendingKey(..) --- , TransparentType(..) --- , ZcashNet(..) --- , getTransparentPrefix --- , getValue --- ) + ( AccountId + , CoinType(..) + , ExchangeAddress(..) + , RawData(..) + , Scope(..) + , Seed(..) + , ToBytes(..) + , TransparentAddress(..) + , TransparentReceiver(..) + , TransparentSpendingKey(..) + , TransparentType(..) + , ZcashNet(..) + , getTransparentPrefix + , getValue + ) import ZcashHaskell.Utils (decodeBech32, encodeBech32m) -- | Required for `TransparentReceiver` encoding and decoding @@ -103,7 +106,76 @@ genTransparentReceiver i scope xprvk = do ScriptAddress j -> return $ TransparentReceiver P2SH $ fromBinary j _anyOtherKind -> throwIO $ userError "Unsupported transparent address type" --- | Generate a transparent receiver +-- | Generate a transparent receiver from a Full Transparent Node +genTransparentReceiverFvk :: + Int -- ^ The index of the address to be created + -> Scope -- ^ `External` for wallet addresses or `Internal` for change addresses + -> BS.ByteString -- ^ The transparent public key + -> IO (Maybe TransparentReceiver) +genTransparentReceiverFvk i scope pubkey = do + ioCtx <- createContext + let s = + case scope of + External -> 0 + Internal -> 1 + let path = Deriv :/ s :/ fromIntegral i :: SoftPath + let fp' = textToFingerprint "f09f8fbe" + case fp' of + Left e -> fail "couldn't get fingerprint" + Right fp -> do + let x = importPubKey ioCtx $ BS.takeEnd 33 pubkey + case x of + Nothing -> fail "couldn't get pubkey" + Just pk -> do + let prepKey = + XPubKey + 3 + fp + 0 + (Bi.decode $ BSL.fromStrict $ BS.take 32 pubkey) + pk + let childPubKey = derivePubPath ioCtx path prepKey + let x = xPubAddr ioCtx childPubKey + case x of + PubKeyAddress k -> + return $ Just $ TransparentReceiver P2PKH $ fromBinary k + ScriptAddress j -> + return $ Just $ TransparentReceiver P2SH $ fromBinary j + _anyOtherKind -> return Nothing + +-- | Generate a transparent receiver from a Incoming Transparent Node +genTransparentReceiverIvk :: + Int -- ^ The index of the address to be created + -> BS.ByteString -- ^ The transparent public key + -> IO (Maybe TransparentReceiver) +genTransparentReceiverIvk i pubkey = do + ioCtx <- createContext + let path = Deriv :/ fromIntegral i :: SoftPath + let fp' = textToFingerprint "f09f8fbe" + case fp' of + Left e -> fail "couldn't get fingerprint" + Right fp -> do + let x = importPubKey ioCtx $ BS.takeEnd 33 pubkey + case x of + Nothing -> fail "couldn't get pubkey" + Just pk -> do + let prepKey = + XPubKey + 3 + fp + 0 + (Bi.decode $ BSL.fromStrict $ BS.take 32 pubkey) + pk + let childPubKey = derivePubPath ioCtx path prepKey + let x = xPubAddr ioCtx childPubKey + case x of + PubKeyAddress k -> + return $ Just $ TransparentReceiver P2PKH $ fromBinary k + ScriptAddress j -> + return $ Just $ TransparentReceiver P2SH $ fromBinary j + _anyOtherKind -> return Nothing + +-- | Generate a transparent spending key genTransparentSecretKey :: Int -- ^ The index of the address to be created -> Scope -- ^ `External` for wallet addresses or `Internal` for change addresses diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index d478dc4..e80250c 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -685,13 +685,19 @@ data ValidAddress | Exchange !ExchangeAddress deriving stock (Eq, Prelude.Show) +-- | A type to handle user-entered viewing keys +data ValidVk + = FullVk !UnifiedFullViewingKey + | IncomingVk !UnifiedIncomingViewingKey + deriving stock (Eq, Prelude.Show, Read) + -- | Type to represent a Unified Full Viewing Key data UnifiedFullViewingKey = UnifiedFullViewingKey { net :: !Word8 -- ^ Number representing the network the key belongs to. @1@ for @mainnet@, @2@ for @testnet@ and @3@ for @regtestnet@. , o_key :: !BS.ByteString -- ^ Raw bytes of the Orchard Full Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) , s_key :: !BS.ByteString -- ^ Raw bytes of the Sapling Full Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) , t_key :: !BS.ByteString -- ^ Raw bytes of the P2PKH chain code and public key as specified in [ZIP-316](https://zips.z.cash/zip-0316) - } deriving stock (Eq, Prelude.Show, GHC.Generic) + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct UnifiedFullViewingKey @@ -702,7 +708,7 @@ data UnifiedIncomingViewingKey = UnifiedIncomingViewingKey , i_o_key :: !BS.ByteString -- ^ Raw bytes of the Orchard Incoming Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) , i_s_key :: !BS.ByteString -- ^ Raw bytes of the Sapling Incoming Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) , i_t_key :: !BS.ByteString -- ^ Raw bytes of the P2PKH chain code and public key as specified in [ZIP-316](https://zips.z.cash/zip-0316) - } deriving stock (Eq, Prelude.Show, GHC.Generic) + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index 03db2b5..2c8ac0d 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -135,7 +135,7 @@ createTransaction :: -> IO (Either TxError HexString) createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing znet bh build = do txResult <- - withBorshBufferOfInitSize 51200 $ + withBorshBufferOfInitSize 102400 $ rustWrapperCreateTx (hexBytes sapAnchor) (hexBytes orchAnchor) diff --git a/test/Spec.hs b/test/Spec.hs index 207b05a..b7f4b41 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -22,10 +22,13 @@ import C.Zcash (rustWrapperUADecode) import Control.Exception (throwIO) import Control.Monad.IO.Class (liftIO) +import Crypto.Secp256k1 import Data.Aeson +import qualified Data.Binary as Bi import Data.Bool (Bool(True)) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C +import qualified Data.ByteString.Lazy as BSL import Data.Either (isRight) import Data.Foldable (sequenceA_) import Data.HexString @@ -46,7 +49,8 @@ import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck import ZcashHaskell.Keys - ( deriveUfvk + ( deriveFullTransparentNode + , deriveUfvk , deriveUivk , generateWalletSeedPhrase , getWalletSeed @@ -56,9 +60,15 @@ import ZcashHaskell.Sapling ( decodeSaplingAddress , decodeSaplingOutput , decodeSaplingOutputEsk + , decodeSaplingOutputFvk + , decodeSaplingOutputIvk + , deriveSaplingFvk , encodeSaplingAddress , genSaplingInternalAddress + , genSaplingInternalAddressFvk , genSaplingPaymentAddress + , genSaplingReceiverFvk + , genSaplingReceiverIvk , genSaplingSpendingKey , getSaplingFrontier , getSaplingNotePosition @@ -109,6 +119,9 @@ import ZcashHaskell.Types , TransparentType(..) , UnifiedAddress(..) , UnifiedFullViewingKey(..) + , UnifiedIncomingViewingKey(..) + , ValidAddress(..) + , ValidVk(..) , ZcashNet(..) , ZebraTxResponse(..) , decodeHexText @@ -1176,24 +1189,207 @@ main = do let sK = genSaplingSpendingKey (fromJust seed) MainNetCoin 0 it "Generate FVK" $ do tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0 - case oK of - Nothing -> assertFailure "Failed to generate Orchard SK" - Just o -> - case sK of - Nothing -> assertFailure "Failed to generate Sapling SK" - Just s -> do - fvk <- deriveUfvk MainNet o s tK - decodeUfvk (E.encodeUtf8 fvk) `shouldNotBe` Nothing + FullVk fvk <- deriveUfvk MainNet oK sK (Just tK) + net fvk `shouldBe` 1 it "Generate IVK" $ do tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0 - case oK of - Nothing -> assertFailure "Failed to generate Orchard SK" - Just o -> - case sK of - Nothing -> assertFailure "Failed to generate Sapling SK" - Just s -> do - ivk <- deriveUivk MainNet o s tK - decodeUivk (E.encodeUtf8 ivk) `shouldNotBe` Nothing + IncomingVk ivk <- deriveUivk MainNet oK sK (Just tK) + i_net ivk `shouldBe` 1 + describe "Use viewing keys" $ do + describe "FVKs" $ do + let fvk = + decodeUfvk + "uviewtest1jna46ql5qns5rlg99jgs6mhf0j9tk8zxvqsm472scgvmj0vs0rqv2kvdf626gftx7dgn2tltyf0s200gvjlsdvz5celpue9wxxw78txswqmayxc3pfrt5fs5frvr3ep0jrjg8euahqzc63yx9sy4z8lql4ev6q3asptl9rhsfzzrup2g5slwnlvy3dgft44jw3l08xtzypjmsrwxskgnp5s03xlc2kg5520a25pa6fdjxhzutam4wkwr6mh4zeq3qndpks8dk0y90y7gucgsp0j5k2xnhh90m3krk5glz4794dj93pf59h85dqms6337f85ccvpxhays94kvsj2hyjsltf52tygqs8y0vp2yf39drxl687the6xkp8nxkfffc3kqlkhw53t5plplde0vk9rwv340ys04gg48fs0pxfp35rvt2f2pvxjmgmln6lp5k2yzkm0r87k89p6xqv68a6uyfpsauswh9fsckfqey02pjedz5gs934qa" + let addrFromWallet = + "utest1act45pg36s7345emmp8lpxx560d4tjy2ef7vzzqvdqmt8d5gjxn7x4aarskvqzqccgyjrv5gc4mr6nf2elz6ylpu8hq3rgm0gj43jkl3elrugr49swlk0pv5edvcgnmhrqswkck6kvswvr89h2q0m6gtwktxzdkjp80c86nlp7x02kd0ttpsylsjddk488nmagtj85xclluug793h8n" + let cua = + "utest1zcjhrp39ux52ype4j6055fngdufet0h2d6hx564s48cgnzthm3xma3eca4x8sh5a89jk88nerngvy5uq9tyxgxq552k64rs93ell63f8sd2rurhn34lr6fjznw64uf473mehpn39gy2k0m86r3gpp5lcdh2senl7kwgl6hku03n0gpqvcfc7c48kjv9z49yp52etntgwkltsz3zvj0m" + let oa = + OrchardAction + (hexString + "33d6bf1d78f6414b725b5b43bfbb92b460d81cf04a352831d74bbc3c3aa86c2a") + (hexString + "ce6d2090bf747ddce410ad967ea98e727120710f9814a6d77ebdd593c6f9660b") + (hexString + "567be1d3c5e2b8aba20fba30edd357810eb40647f1f5f9bb4e23691a9c174235") + (hexString + "b4ee13a27e1b7f2674c7b3924c2178b4c010c05750b78b65cb741b10476d1c24") + (hexString + "e8c37d9a2ea88f2ef266fed77753744da9afedf2530940b15ad882cc187a69d6116c5c34cdaf94e1f178ec50ef95d49b46ab6a7206c2a6b57d9d55f65cc7f5f57ccb91d71e827af5286897a4c8ca9c3fc2dad60208adbc8bd3d81eb5febf4c3c75e69b83627fdff730a946bb2e6e6703c4c676675e34ece1b073ce61490298a93503fa10f2b86fbd1274825c72e26b2340d1ed338d3b254803614b2fc9778155c988516d6ce1f334aeca076063c06588961c0d7dfa1bafd59cbca1782fcd863fce1f25abdf151c9718dce3708f8cede130d9e785ed4798aebfca8fec386f4a4d38a75c7b054c453676ad9ce7de32f4c4687f6932686f52e381bd41508a468ca684ea2755d56522a7b4138f58d0b10e1195a6fe27393ca382d0525a74acde5e0f6f2cb8ff3d74221f2a02fe2fd1874a2cf6f542bfa8f0107a21728249ca549bbba66a5422fd9aeaaeff09d1edfecb711c15290c9e8d031baf41fe47a5da3f9a614016f45886fb4d3ea2795802bf7785c1c9e3de15dc18300891f6abb9569851a7c4bf4cd054f8f593969b42c80968bf732ebaa90abf1a7614d1d325e8ed930161e0fbf82333c96cea455743c3e3e3cd1cdd754383a6f2364eacc162059e31885045eee7f2d60b068e78e29490a4df0f34e690b3bb17923669b828d245727bf11b0fe1e0fc4b97b62be37f7a7944db5b5b13221b0000f74c3cdcf9c246e60e77327c4f853f4037eeb2feb9f5d84961e4321c54c30d193866897872b31113a5f819dc2a264d2ef7b91edd16504ad75b6ee622dad79dadaadcd005215932d66232738a7dd8d2") + (hexString + "0b7be09c8f69e068d3277f345a0a4bf11057ff4c46e7657bb8a61a1c2b4c0d61e8e561131f935053f02cb46360f13179dc4647a243fabdd9ac59ba9ac2529a3b7af0ea5020d2b4c17466ebf309448491") + (hexString + "81939f32c4ca40c0a4c9334cffd68339e207afc322b533cb9cbb7b156758dc94") + (hexString + "44e3d8c4fbac16fc0edfd634b396b0a4822fc9a2cfdc8ec293a5c6ffc2001e96d2840c303463e3bc9add8c9263c56569e5b7ac14cf8d84316735f4b77493c136") + let so = + ShieldedOutput + (hexString + "dae2e33897847cc968d4719efc6ff6ea69c7ea8f8567883ba891c3d99b7fc7f3") + (hexString + "3d7c752b89af7dd63430d458d54c60643533ed6db9c57f55131eb7303daf7844") + (hexString + "8f7bd84a946a9068cb2030dd8d1f7446bba2ff22a5cce5ac97cc5c46f1c9bba9") + (hexString + "bc7b212e7c0a598a1237493a014688ee1232f631928e50583df6a1980adefb867dca6f97aa468f3c3a3882db8f359fc0d29205a808173645c22ba8a815e307fec633ffd01d34830e639e4dda548cd6e3ea32054206ac77e60ae09b0bf911066caf2be0bc8e3936edfa3e05c74f2e8a8bf5bbd8867eb83fd7fe24107612f6a0eaa586c6997b19fbe43c3b7d3f8cd5f40ae1a5431cf9d0d336516fb22ce4e295f77326ada3b453b9813a4a63981a52b9ba65af43cc6ec198b384417247b8f1bb15199fba5e90e435c946d03f97c62f7b40fbfef85e456dcb8fdaec94585a600cb40b2ed6c23c08eba5115719944528c156bea996b993d6e1730a17c6cf036deee43a18bd6afbd6e0f55f6c35ab5557df8e1356866514784ab4dd5ff158f0c5bfb6011adefae26c6dcee7472b7086680a72859e231829b7cf408683ced04171065e8740abde2b528c790fbec34d80e15ca869aaa100640c7ab167d7538a48174ea7caac91297455f85a41a03ab760565dd899b6b10b7034b221f4390373bd2dc1e3766629f7d5b606449ef07057995da06a63b6bb07aa117fd26e7154e55636ab8df21c3e3665574899eb1186d2106a55cc20b7eee2bc9b5970acddbac4a74f2f5599d7b42c21e65f340363748b208e61800ce6317e43c7e55757e1c9210bc716a93edb5ca2ed26ead4c14510086fb6752a8bfa089787360cca5c6b053744ad1487f7032ba027ba7758ce842a372b2f7dd418ecbd6353ca199629faa8848dfb7f228610aa2f76324eb7092ca05c6e2e7c7e9972cf33009c716ad43adc492a25fb8d1d67e72f") + (hexString + "ebe23f6ad66a16f9e5faece7139ae55aae93631b8b36ba2b5d21885f29b39dab224549afef0137c612d0c7a8ec597e98ea5a08334392079e5ea5c061cef2ebe296e71b6845e6bea09291bba505a76a03") + (hexString + "ad238bb5d88ccc438998bbcda831b499775f76c706f67bd9e56b8c3d6a6479afcf562a459c8db54003b641f1ac83d2dfa83baf8e04d6c2ab335eec290b3f581b0063cb088b53eb30b372ce80ac86a904448b9766d02624caa42fd9828ba4912e075b786fcbed921bc4cd25555031b2cd92509894d8ade8f4da92007b010b506c5664802e4e80c3363cd6e16c2a67abdcb4f3aef7638095091ab74cf79b6dcb919f7b38547e9576a7512f0f128504bb3461b8fee69ab7774320f56b2af13a6c9d") + it "Orchard external receiver from fvk" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> + genOrchardReceiverFvk 0 External (o_key k) `shouldNotBe` Nothing + it "Orchard internal receiver from fvk" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> + genOrchardReceiverFvk 0 Internal (o_key k) `shouldNotBe` Nothing + it "Decrypt Orchard action with FVK" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> + a_nullifier <$> + decryptOrchardActionFvk k External oa `shouldBe` + Just (hexString "00") + it "Sapling external receiver from fvk" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> genSaplingReceiverFvk 0 (s_key k) `shouldNotBe` Nothing + it "Sapling internal receiver from fvk" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> + genSaplingInternalAddressFvk (s_key k) `shouldNotBe` Nothing + it "Decode external Sapling with fvk" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> + a_nullifier <$> + decodeSaplingOutputFvk (s_key k) so External 234878 `shouldBe` + Just (hexString "00") + it "Transparent external receiver from fvk" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> do + recv <- genTransparentReceiverFvk 0 External (t_key k) + recv `shouldNotBe` Nothing + it "Transparent internal receiver from fvk" $ do + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> do + recv <- genTransparentReceiverFvk 0 Internal (t_key k) + recv `shouldNotBe` Nothing + it "Generate UA from FVK" $ do + let parsedUA = parseAddress addrFromWallet + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> do + case parsedUA of + Nothing -> assertFailure "Failed to parse UA" + Just va -> do + case va of + Unified ua -> do + let orec = genOrchardReceiverFvk 0 External (o_key k) + let srec = genSaplingReceiverFvk 0 (s_key k) + trec <- genTransparentReceiverFvk 0 External (t_key k) + let myUa = UnifiedAddress TestNet orec srec trec + myUa `shouldBe` ua + _any -> assertFailure "Address to UA" + it "Generate change UA from FVK" $ do + let parsedCua = parseAddress cua + case fvk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> do + case parsedCua of + Nothing -> assertFailure "Failed to parse UA" + Just va -> do + case va of + Unified ua -> do + let orec = genOrchardReceiverFvk 0 Internal (o_key k) + let srec = genSaplingInternalAddressFvk (s_key k) + trec <- genTransparentReceiverFvk 0 Internal (t_key k) + let myUa = UnifiedAddress TestNet orec srec trec + myUa `shouldBe` ua + _any -> assertFailure "Address to UA" + describe "IVKs" $ do + let ivk = + decodeUivk + "uivktest10yy38f5v5sz5hnne93flkrpfqr4geyfuaq97u4fe9zkrng20ppeqsu7a6fkfttn0phjj9kutlnn4nzvfls233xujcv3fw6ax9w8pqzsw792py78dl8p4cyhv3eyusu589382qwf4pwz3vs7n6pv4qj7j6apg5nvw4yl4u5g033tpr2a7jjzt3f3khjm09wj7er7myr8vy04595nj6pqcdp3ndp98ckg82y0w08d8wnx7wynrzhmqrxh9pf0m50tj332vjld08uz027xwyegakyzem745y462khv9xzlc4h5rxwsqfklvak3x26excpcjn54e3cd2zttxujnuzv3rrtrpuld9e2" + let addrFromWallet = + "utest1act45pg36s7345emmp8lpxx560d4tjy2ef7vzzqvdqmt8d5gjxn7x4aarskvqzqccgyjrv5gc4mr6nf2elz6ylpu8hq3rgm0gj43jkl3elrugr49swlk0pv5edvcgnmhrqswkck6kvswvr89h2q0m6gtwktxzdkjp80c86nlp7x02kd0ttpsylsjddk488nmagtj85xclluug793h8n" + let oa = + OrchardAction + (hexString + "33d6bf1d78f6414b725b5b43bfbb92b460d81cf04a352831d74bbc3c3aa86c2a") + (hexString + "ce6d2090bf747ddce410ad967ea98e727120710f9814a6d77ebdd593c6f9660b") + (hexString + "567be1d3c5e2b8aba20fba30edd357810eb40647f1f5f9bb4e23691a9c174235") + (hexString + "b4ee13a27e1b7f2674c7b3924c2178b4c010c05750b78b65cb741b10476d1c24") + (hexString + "e8c37d9a2ea88f2ef266fed77753744da9afedf2530940b15ad882cc187a69d6116c5c34cdaf94e1f178ec50ef95d49b46ab6a7206c2a6b57d9d55f65cc7f5f57ccb91d71e827af5286897a4c8ca9c3fc2dad60208adbc8bd3d81eb5febf4c3c75e69b83627fdff730a946bb2e6e6703c4c676675e34ece1b073ce61490298a93503fa10f2b86fbd1274825c72e26b2340d1ed338d3b254803614b2fc9778155c988516d6ce1f334aeca076063c06588961c0d7dfa1bafd59cbca1782fcd863fce1f25abdf151c9718dce3708f8cede130d9e785ed4798aebfca8fec386f4a4d38a75c7b054c453676ad9ce7de32f4c4687f6932686f52e381bd41508a468ca684ea2755d56522a7b4138f58d0b10e1195a6fe27393ca382d0525a74acde5e0f6f2cb8ff3d74221f2a02fe2fd1874a2cf6f542bfa8f0107a21728249ca549bbba66a5422fd9aeaaeff09d1edfecb711c15290c9e8d031baf41fe47a5da3f9a614016f45886fb4d3ea2795802bf7785c1c9e3de15dc18300891f6abb9569851a7c4bf4cd054f8f593969b42c80968bf732ebaa90abf1a7614d1d325e8ed930161e0fbf82333c96cea455743c3e3e3cd1cdd754383a6f2364eacc162059e31885045eee7f2d60b068e78e29490a4df0f34e690b3bb17923669b828d245727bf11b0fe1e0fc4b97b62be37f7a7944db5b5b13221b0000f74c3cdcf9c246e60e77327c4f853f4037eeb2feb9f5d84961e4321c54c30d193866897872b31113a5f819dc2a264d2ef7b91edd16504ad75b6ee622dad79dadaadcd005215932d66232738a7dd8d2") + (hexString + "0b7be09c8f69e068d3277f345a0a4bf11057ff4c46e7657bb8a61a1c2b4c0d61e8e561131f935053f02cb46360f13179dc4647a243fabdd9ac59ba9ac2529a3b7af0ea5020d2b4c17466ebf309448491") + (hexString + "81939f32c4ca40c0a4c9334cffd68339e207afc322b533cb9cbb7b156758dc94") + (hexString + "44e3d8c4fbac16fc0edfd634b396b0a4822fc9a2cfdc8ec293a5c6ffc2001e96d2840c303463e3bc9add8c9263c56569e5b7ac14cf8d84316735f4b77493c136") + let so = + ShieldedOutput + (hexString + "dae2e33897847cc968d4719efc6ff6ea69c7ea8f8567883ba891c3d99b7fc7f3") + (hexString + "3d7c752b89af7dd63430d458d54c60643533ed6db9c57f55131eb7303daf7844") + (hexString + "8f7bd84a946a9068cb2030dd8d1f7446bba2ff22a5cce5ac97cc5c46f1c9bba9") + (hexString + "bc7b212e7c0a598a1237493a014688ee1232f631928e50583df6a1980adefb867dca6f97aa468f3c3a3882db8f359fc0d29205a808173645c22ba8a815e307fec633ffd01d34830e639e4dda548cd6e3ea32054206ac77e60ae09b0bf911066caf2be0bc8e3936edfa3e05c74f2e8a8bf5bbd8867eb83fd7fe24107612f6a0eaa586c6997b19fbe43c3b7d3f8cd5f40ae1a5431cf9d0d336516fb22ce4e295f77326ada3b453b9813a4a63981a52b9ba65af43cc6ec198b384417247b8f1bb15199fba5e90e435c946d03f97c62f7b40fbfef85e456dcb8fdaec94585a600cb40b2ed6c23c08eba5115719944528c156bea996b993d6e1730a17c6cf036deee43a18bd6afbd6e0f55f6c35ab5557df8e1356866514784ab4dd5ff158f0c5bfb6011adefae26c6dcee7472b7086680a72859e231829b7cf408683ced04171065e8740abde2b528c790fbec34d80e15ca869aaa100640c7ab167d7538a48174ea7caac91297455f85a41a03ab760565dd899b6b10b7034b221f4390373bd2dc1e3766629f7d5b606449ef07057995da06a63b6bb07aa117fd26e7154e55636ab8df21c3e3665574899eb1186d2106a55cc20b7eee2bc9b5970acddbac4a74f2f5599d7b42c21e65f340363748b208e61800ce6317e43c7e55757e1c9210bc716a93edb5ca2ed26ead4c14510086fb6752a8bfa089787360cca5c6b053744ad1487f7032ba027ba7758ce842a372b2f7dd418ecbd6353ca199629faa8848dfb7f228610aa2f76324eb7092ca05c6e2e7c7e9972cf33009c716ad43adc492a25fb8d1d67e72f") + (hexString + "ebe23f6ad66a16f9e5faece7139ae55aae93631b8b36ba2b5d21885f29b39dab224549afef0137c612d0c7a8ec597e98ea5a08334392079e5ea5c061cef2ebe296e71b6845e6bea09291bba505a76a03") + (hexString + "ad238bb5d88ccc438998bbcda831b499775f76c706f67bd9e56b8c3d6a6479afcf562a459c8db54003b641f1ac83d2dfa83baf8e04d6c2ab335eec290b3f581b0063cb088b53eb30b372ce80ac86a904448b9766d02624caa42fd9828ba4912e075b786fcbed921bc4cd25555031b2cd92509894d8ade8f4da92007b010b506c5664802e4e80c3363cd6e16c2a67abdcb4f3aef7638095091ab74cf79b6dcb919f7b38547e9576a7512f0f128504bb3461b8fee69ab7774320f56b2af13a6c9d") + it "Orchard external receiver from ivk" $ do + case ivk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> genOrchardReceiverIvk 0 (i_o_key k) `shouldNotBe` Nothing + it "Decrypt Orchard action with IVK" $ do + case ivk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> decryptOrchardActionIvk k oa `shouldNotBe` Nothing + it "Sapling external receiver from ivk" $ do + case ivk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> genSaplingReceiverIvk 0 (i_s_key k) `shouldNotBe` Nothing + it "Decode external Sapling with ivk" $ do + case ivk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> + decodeSaplingOutputIvk (i_s_key k) so `shouldNotBe` Nothing + it "Transparent external receiver from ivk" $ do + case ivk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> do + recv <- genTransparentReceiverIvk 0 (i_t_key k) + recv `shouldNotBe` Nothing + it "Generate UA from IVK" $ do + let parsedUA = parseAddress addrFromWallet + case ivk of + Nothing -> assertFailure "Failed to parse VK" + Just k -> do + case parsedUA of + Nothing -> assertFailure "Failed to parse UA" + Just va -> do + case va of + Unified ua -> do + let orec = genOrchardReceiverIvk 0 (i_o_key k) + let srec = genSaplingReceiverIvk 0 (i_s_key k) + trec <- genTransparentReceiverIvk 0 (i_t_key k) + let myUa = UnifiedAddress TestNet orec srec trec + myUa `shouldBe` ua + _any -> assertFailure "Address to UA" -- | Properties prop_PhraseLength :: Property diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 261e9a1..6cef280 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: zcash-haskell -version: 0.7.8.1 +version: 0.8.1.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain