From 80478c9b6fc41ee20919dfb3fa6a97ad809a90f0 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 14 Jun 2023 09:55:52 -0500 Subject: [PATCH] Add Sapling VK validation --- Makefile | 2 +- librustzcash-wrapper/Cargo.toml | 2 ++ librustzcash-wrapper/src/lib.rs | 55 ++++++++++++++++++++++++++++++--- src/C/Zcash.chs | 6 ++++ src/HaskellZcash/Sapling.hs | 6 +++- test/Spec.hs | 30 ++++++++++++++++++ 6 files changed, 95 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2c73f57..77d8055 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ rustlib := librustzcash-wrapper/target/x86_64-unknown-linux-gnu/debug all: haskell -haskell: src/Zcash.hs src/C/Zcash.chs package.yaml stack.yaml $(rustlib)/rustzcash_wrapper.h $(rustlib)/librustzcash_wrapper.a $(rustlib)/librustzcash_wrapper.so $(rustlib)/rustzcash_wrapper-uninstalled.pc +haskell: src/HaskellZcash/Orchard.hs src/HaskellZcash/Sapling.hs src/HaskellZcash/Types.hs src/HaskellZcash/Utils.hs src/C/Zcash.chs package.yaml stack.yaml $(rustlib)/rustzcash_wrapper.h $(rustlib)/librustzcash_wrapper.a $(rustlib)/librustzcash_wrapper.so $(rustlib)/rustzcash_wrapper-uninstalled.pc stack build $(rustlib)/rustzcash_wrapper.h: librustzcash-wrapper/src/lib.rs librustzcash-wrapper/Cargo.toml diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index 491d1d4..5d1340c 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -14,6 +14,8 @@ borsh = "0.10" bech32 = "0.9.1" orchard = "0.4.0" zcash_note_encryption = "0.3.0" +zcash_primitives = "0.12.0" +zcash_client_backend = "0.9.0" [features] capi = [] diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 0aaf6ee..93a1245 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -16,12 +16,16 @@ use haskell_ffi::{ FromHaskell, HaskellSize, ToHaskell }; +use zcash_primitives::{sapling::keys::FullViewingKey as SaplingViewingKey, zip32::DiversifiableFullViewingKey}; + use zcash_address::{ Network, unified::{Address, Encoding, Ufvk, Container, Fvk}, ZcashAddress }; +use zcash_client_backend::keys::sapling::ExtendedFullViewingKey; + use orchard::{ Action, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope}, @@ -35,7 +39,10 @@ use zcash_note_encryption; use bech32::{ decode, - u5 + u5, + FromBase32, + ToBase32, + Variant }; pub enum RW {} @@ -124,6 +131,19 @@ impl Hufvk { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Hsvk { + vk: Vec, + ovk: Vec +} + +impl ToHaskell for Hsvk { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + fn to_array(v: Vec) -> [T; N] { v.try_into().unwrap_or_else(|v: Vec| panic!("Expected a Vec of length {} but it was {}", N, v.len())) } @@ -175,9 +195,36 @@ pub extern "C" fn rust_wrapper_bech32decode( out_len: &mut usize ) { let input: String = marshall_from_haskell_var(input, input_len, RW); - let (hrp, bytes) = bech32::decode_without_checksum(&input).unwrap(); - let rd = RawData {hrp: hrp.into(), bytes: bytes.iter().map(|&x| bech32::u5::to_u8(x)).collect()}; - marshall_to_haskell_var(&rd, out, out_len, RW); + let (hrp, bytes, variant) = bech32::decode(&input).unwrap(); + let decodedBytes = bech32::decode(&input); + match decodedBytes { + Ok((hrp, bytes, variant)) => { + let rd = RawData {hrp: hrp.into(), bytes: Vec::::from_base32(&bytes).unwrap()}; + marshall_to_haskell_var(&rd, out, out_len, RW); + } + Err(_e) => { + let rd1 = RawData {hrp: "fail".into(), bytes: vec![0]}; + marshall_to_haskell_var(&rd1, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_svk_decode( + input: *const u8, + input_len: usize + ) -> bool { + let input: Vec = marshall_from_haskell_var(input, input_len, RW); + let svk = ExtendedFullViewingKey::read(&*input); + match svk { + Ok(k) => { + true + } + Err(e) => { + print!("{}", e); + false + } + } } #[no_mangle] diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 5c1b875..dac9265 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -59,6 +59,12 @@ import HaskellZcash.Types -> `()' #} +{# fun pure unsafe rust_wrapper_svk_decode as rustWrapperSaplingVkDecode + { toBorshVar* `BS.ByteString'& + } + -> `Bool' +#} + {# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer UnifiedFullViewingKey'& diff --git a/src/HaskellZcash/Sapling.hs b/src/HaskellZcash/Sapling.hs index e77285c..eede6ac 100644 --- a/src/HaskellZcash/Sapling.hs +++ b/src/HaskellZcash/Sapling.hs @@ -1,8 +1,12 @@ module HaskellZcash.Sapling where -import C.Zcash (rustWrapperIsShielded) +import C.Zcash (rustWrapperIsShielded, rustWrapperSaplingVkDecode) import qualified Data.ByteString as BS -- | Check if given bytesting is a valid encoded shielded address isValidShieldedAddress :: BS.ByteString -> Bool isValidShieldedAddress = rustWrapperIsShielded + +-- | Check if given bytestring is a valid Sapling viewing key +isValidSaplingViewingKey :: BS.ByteString -> Bool +isValidSaplingViewingKey = rustWrapperSaplingVkDecode diff --git a/test/Spec.hs b/test/Spec.hs index 4a01f68..0fefb73 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -5,6 +5,7 @@ import qualified Data.ByteString as BS import qualified Data.Text.Encoding as E import Data.Word import HaskellZcash.Orchard +import HaskellZcash.Sapling (isValidSaplingViewingKey, isValidShieldedAddress) import HaskellZcash.Types ( OrchardAction(..) , OrchardDecodedAction(..) @@ -17,6 +18,12 @@ import Test.Hspec main :: IO () main = do hspec $ do + describe "Bech32" $ do + let s = "bech321qqqsyrhqy2a" + let decodedString = decodeBech32 s + it "hrp matches" $ do hrp decodedString `shouldBe` "bech32" + it "data matches" $ do + bytes decodedString `shouldBe` BS.pack ([0x00, 0x01, 0x02] :: [Word8]) describe "F4Jumble" $ do it "jumble a string" $ do let input = @@ -222,6 +229,29 @@ main = do , 0x22 ] :: [Word8] f4UnJumble (BS.pack out) `shouldBe` BS.pack input + describe "Sapling address" $ do + it "succeeds with valid address" $ do + let sa = + "zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvf3sl8" + isValidShieldedAddress sa `shouldBe` True + it "fails with invalid address" $ do + let sa = + "zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvffake" + isValidShieldedAddress sa `shouldBe` False + describe "Decode Sapling VK" $ do + let vk = + "zxviews1qdjagrrpqqqqpq8es75mlu6rref0qyrstchf8dxzeygtsejwfqu8ckhwl2qj5m8am7lmupxk3vkvdjm8pawjpmesjfapvsqw96pa46c2z0kk7letrxf7mkltwz54fwpxc7kc79mm5kce3rwn5ssl009zwsra2spppwgrx25s9k5hq65f69l4jz2tjmqgy0pl49qmtaj3nudk6wglwe2hpa327hydlchtyq9av6wjd6hu68e04ahwk9a9n2kt0kj3nj99nue65awtu5cwwcpjs" + let sa = + "zs1g2ne5w2r8kvalwzngsk3kfzppx3qcx5560pnfmw9rj5xfd3zfg9dkm7hyxnfyhc423fev5wuue4" + let rawKey = decodeBech32 vk + it "is mainnet" $ do hrp rawKey `shouldBe` "zxviews" + it "is valid Sapling raw key" $ do + isValidSaplingViewingKey (bytes rawKey) `shouldBe` True + describe "Decode invalid Sapling VK" $ do + let vk = + "zxviews1qdjagrrpqqqqpq8es75mlu6rref0qyrstchf8dxzeygtsejwfqu8ckhwl2qj5m8am7lmupxk3vkvdjm8pawjpmesjfapvsqw96pa46c2z0kk7letrxf7mkltwz54fwpxc7kc79mm5kce3rwn5ssl009zwsra2spppwgrx25s9k5hq65f69l4jz2tjmqgy0pl49qmtaj3nudk6wglwe2hpa327hydlchtyq9av6wjd6hu68e04ahwk9a9n2kt0kj3nj99nue65awtu5cwwfake" + let rawKey = decodeBech32 vk + it "is not mainnet" $ do hrp rawKey `shouldBe` "fail" describe "Unified address" $ do it "succeeds with correct UA" $ do let ua =