From 7c79ee6163d18d03a6bfc7d703bf650558d4b976 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Thu, 25 Jul 2024 11:09:17 -0500 Subject: [PATCH 01/23] Add JSON instances for ZcashNet --- CHANGELOG.md | 4 ++++ src/ZcashHaskell/Types.hs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24814be..2f3492f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.6.2.2] + +- Added JSON instances for `ZcashNet` + ## [0.6.2.1] ### Changed diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index af60f0f..f806413 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -90,7 +90,7 @@ data ZcashNet = MainNet | TestNet | RegTestNet - deriving (Eq, Prelude.Show, Read) + deriving (Eq, Prelude.Show, Read, GHC.Generic, ToJSON, FromJSON) type AccountId = Int -- 2.34.1 From 9ca702a68e5ac2e9893bf4f6826686cb3ae99a56 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 13 Aug 2024 07:56:25 -0500 Subject: [PATCH 02/23] Add `ValidAddress` --- CHANGELOG.md | 1 + src/ZcashHaskell/Types.hs | 8 ++++++++ zcash-haskell.cabal | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f3492f..64af8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.6.2.2] - Added JSON instances for `ZcashNet` +- Added `ValidAddress` ## [0.6.2.1] diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index f806413..d6bfd2e 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -583,6 +583,14 @@ data RawUA = RawUA deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawUA +-- | A type to handle user-entered addresses +data ValidAddress + = Unified !UnifiedAddress + | Sapling !SaplingAddress + | Transparent !TransparentAddress + | Exchange !ExchangeAddress + deriving stock (Eq, Prelude.Show) + -- | 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@. diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 27924ee..436bf5a 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.6.2.1 +version: 0.6.2.2 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From b7c91e10fe9210a01de914a3fc55e7698d32c085 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 14 Aug 2024 12:32:30 -0500 Subject: [PATCH 03/23] Add functions to compare and validate addresses --- src/ZcashHaskell/Orchard.hs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index d7c3e38..145c503 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -37,6 +37,11 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word import Foreign.Rust.Marshall.Variable +import ZcashHaskell.Sapling (decodeSaplingAddress) +import ZcashHaskell.Transparent + ( decodeExchangeAddress + , decodeTransparentAddress + ) import ZcashHaskell.Types import ZcashHaskell.Utils (encodeBech32, encodeBech32m, f4Jumble) @@ -221,8 +226,33 @@ getOrchardNotePosition :: OrchardWitness -> Integer getOrchardNotePosition = fromIntegral . rustWrapperReadOrchardPosition . hexBytes . orchWit +-- | Update the witness of an Orchard note updateOrchardWitness :: OrchardWitness -> [HexString] -> OrchardWitness updateOrchardWitness wit cmus = OrchardWitness $ withPureBorshVarBuffer $ rustWrapperUpdateOrchardWitness (toBytes $ orchWit wit) (map toBytes cmus) + +-- | Parse a potential Zcash address +parseAddress :: BS.ByteString -> Maybe ValidAddress +parseAddress t = + case isValidUnifiedAddress t of + Nothing -> + case decodeSaplingAddress t of + Nothing -> + case decodeTransparentAddress t of + Nothing -> + case decodeExchangeAddress t of + Nothing -> Nothing + Just x -> Just $ Exchange x + Just t -> Just $ Transparent t + Just s -> Just $ Sapling s + Just u -> Just $ Unified u + +compareAddress :: ValidAddress -> UnifiedAddress -> Bool +compareAddress a u = + case a of + Unified i -> i == u + Sapling s -> s_rec u == Just (sa_receiver s) && ua_net u == net_type s + Transparent t -> t_rec u == Just (ta_receiver t) && ua_net u == ta_network t + Exchange x -> False -- 2.34.1 From 774e135aabc0f5cddae76d93d7a80f8786e46f4e Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 14 Aug 2024 12:32:48 -0500 Subject: [PATCH 04/23] Fix data type --- src/ZcashHaskell/Transparent.hs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 878a7d8..287fe99 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} + -- Copyright 2022-2024 Vergara Technologies LLC -- -- This file is part of Zcash-Haskell. @@ -172,27 +174,27 @@ decodeTransparentAddress taddress = do -- | Encode an Exchange Addresss into HRF from TransparentReceiver encodeExchangeAddress :: ZcashNet -> TransparentReceiver -> Maybe T.Text encodeExchangeAddress net tr = do - case (tr_type tr) of + case tr_type tr of P2PKH -> do case net of MainNet -> do - let vhash = encodeBech32m (BC.pack "tex") (toBytes (tr_bytes tr)) + let vhash = encodeBech32m "tex" (toBytes (tr_bytes tr)) Just vhash TestNet -> do - let vhash = encodeBech32m (BC.pack "textest") (toBytes (tr_bytes tr)) + let vhash = encodeBech32m "textest" (toBytes (tr_bytes tr)) Just vhash - _ -> Nothing + _any -> Nothing -- | Decode an Exchange Address into a ExchangeAddress -decodeExchangeAddress :: T.Text -> Maybe ExchangeAddress +decodeExchangeAddress :: BS.ByteString -> Maybe ExchangeAddress decodeExchangeAddress ex = do - if (T.length ex) > 1 + if BS.length ex > 1 then do - let rawd = decodeBech32 (E.encodeUtf8 ex) - let tMain = BS.unpack (BC.pack "tex") - let tTest = BS.unpack (BC.pack "textest") - let tFail = BS.unpack (BC.pack "fail") - let hr = BS.unpack (hrp rawd) + let rawd = decodeBech32 ex + let tMain = "tex" + let tTest = "textest" + let tFail = "fail" + let hr = hrp rawd if hr /= tFail then do let transparentReceiver = bytes rawd -- 2.34.1 From c8f411fcdd0e987dc0c4b544d1ad4dcfb12dd97e Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 20 Aug 2024 15:50:07 -0500 Subject: [PATCH 05/23] add JSON instances for `Transaction` --- CHANGELOG.md | 1 + src/ZcashHaskell/Types.hs | 71 +++++++++++++++++++++++++++++++++++++++ zcash-haskell.cabal | 1 + 3 files changed, 73 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64af8aa..5d7630c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.6.2.2] - Added JSON instances for `ZcashNet` +- Added JSON instances for `Transaction` - Added `ValidAddress` ## [0.6.2.1] diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index d6bfd2e..9884b7f 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -38,6 +38,7 @@ import Data.Maybe (fromJust, fromMaybe) import Data.Structured import qualified Data.Text as T import qualified Data.Text.Encoding as E +import qualified Data.Vector as V import Data.Word import qualified GHC.Generics as GHC import qualified Generics.SOP as SOP @@ -133,6 +134,18 @@ data Transaction = Transaction , tx_orchardBundle :: !(Maybe OrchardBundle) } deriving (Prelude.Show, Eq, Read) +instance ToJSON Transaction where + toJSON (Transaction t h c e tb sb ob) = + object + [ "txid" .= t + , "height" .= h + , "confirmations" .= c + , "expiry" .= e + , "transparent" .= tb + , "sapling" .= sb + , "orchard" .= ob + ] + -- | The transparent portion of a Zcash transaction data TransparentBundle = TransparentBundle { tb_vin :: ![H.TxIn] @@ -140,6 +153,10 @@ data TransparentBundle = TransparentBundle , tb_coinbase :: !Bool } deriving (Eq, Prelude.Show, Read) +instance ToJSON TransparentBundle where + toJSON (TransparentBundle vin vout c) = + object ["vin" .= vin, "vout" .= vout, "coinbase" .= c] + -- | Read a raw transparent bundle into the Haskell type fromRawTBundle :: RawTBundle -> Maybe TransparentBundle fromRawTBundle rtb = @@ -324,6 +341,10 @@ data SaplingBundle = SaplingBundle , sbSig :: !HexString } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) +instance ToJSON SaplingBundle where + toJSON (SaplingBundle s o v sig) = + object ["spends" .= s, "outputs" .= o, "value" .= v, "sig" .= sig] + fromRawSBundle :: RawSBundle -> Maybe SaplingBundle fromRawSBundle b = if zsb_empty b @@ -355,6 +376,17 @@ data OrchardBundle = OrchardBundle , obSig :: !HexString } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) +instance ToJSON OrchardBundle where + toJSON (OrchardBundle a f v an p s) = + object + [ "actions" .= a + , "flags" .= f + , "value" .= v + , "anchor" .= an + , "proof" .= p + , "sig" .= s + ] + fromRawOBundle :: RawOBundle -> Maybe OrchardBundle fromRawOBundle b = if zob_empty b @@ -377,6 +409,10 @@ data OrchardFlags = OrchardFlags deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardFlags +instance ToJSON OrchardFlags where + toJSON (OrchardFlags s o) = + Data.Aeson.Array $ V.fromList [Data.Aeson.Bool s, Data.Aeson.Bool o] + -- | Type for the response from the `zebrad` RPC method `getinfo` data ZebraGetInfo = ZebraGetInfo { zgi_build :: !T.Text @@ -501,6 +537,17 @@ data ShieldedSpend = ShieldedSpend deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct ShieldedSpend +instance ToJSON ShieldedSpend where + toJSON (ShieldedSpend cv a n rk p au) = + object + [ "cv" .= cv + , "anchor" .= a + , "nullifier" .= n + , "rk" .= rk + , "proof" .= p + , "spendAuthSig" .= au + ] + instance FromJSON ShieldedSpend where parseJSON = withObject "ShieldedSpend" $ \obj -> do @@ -525,6 +572,17 @@ data ShieldedOutput = ShieldedOutput deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct ShieldedOutput +instance ToJSON ShieldedOutput where + toJSON (ShieldedOutput c cm e enc o p) = + object + [ "cv" .= c + , "cmu" .= cm + , "ephemeralKey" .= e + , "encCiphertext" .= enc + , "outCiphertext" .= o + , "proof" .= p + ] + instance FromJSON ShieldedOutput where parseJSON = withObject "ShieldedOutput" $ \obj -> do @@ -617,6 +675,19 @@ data OrchardAction = OrchardAction deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardAction +instance ToJSON OrchardAction where + toJSON (OrchardAction n r c e en o cv a) = + object + [ "nullifier" .= n + , "rk" .= r + , "cmx" .= c + , "ephemeralKey" .= e + , "encCiphertext" .= en + , "outCiphertext" .= o + , "cv" .= cv + , "spendAuthSig" .= a + ] + instance FromJSON OrchardAction where parseJSON = withObject "OrchardAction" $ \obj -> do diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 436bf5a..33c6140 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -59,6 +59,7 @@ library , text , haskoin-core , secp256k1-haskell >= 1.1 + , vector , utf8-string build-tool-depends: c2hs:c2hs -- 2.34.1 From 85a4741dcb32999a265f879327c1b56c31e307c8 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 30 Aug 2024 11:02:52 -0500 Subject: [PATCH 06/23] fix: decode unified addresses with no transparent receivers ensures that a unified address that does not contain a transparent receiver is properly represented in the type --- CHANGELOG.md | 6 ++++++ src/ZcashHaskell/Orchard.hs | 4 ++-- test/Spec.hs | 2 +- zcash-haskell.cabal | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7630c..6a1a508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.6.2.3] + +### Fixed + +- Decoding of unified addresses with no transparent receivers + ## [0.6.2.2] - Added JSON instances for `ZcashNet` diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 145c503..6bfe0c4 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -103,9 +103,9 @@ isValidUnifiedAddress str = (if BS.length (raw_s x) == 43 then Just $ SaplingReceiver (raw_s x) else Nothing) - (if not (BS.null (raw_t x)) + (if BS.length (raw_t x) > 1 then Just $ TransparentReceiver P2PKH (fromRawBytes $ raw_t x) - else if not (BS.null (raw_to x)) + else if BS.length (raw_to x) > 1 then Just $ TransparentReceiver P2SH (fromRawBytes $ raw_to x) else Nothing) diff --git a/test/Spec.hs b/test/Spec.hs index 7b0aac0..67ca3ce 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1143,7 +1143,7 @@ main = do case exch of Nothing -> assertFailure "Failed to encode Exchange address" Just addr -> do - let eadr = decodeExchangeAddress addr + let eadr = decodeExchangeAddress (E.encodeUtf8 addr) eadr `shouldNotBe` Nothing describe "Witness updates" $ do it "Sapling" $ do diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 33c6140..b521e58 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.6.2.2 +version: 0.6.2.3 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From 5a8cd44fbca5bbac613fe5e4a75e64635a45e2f2 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Mon, 16 Sep 2024 13:36:49 -0500 Subject: [PATCH 07/23] feat: implement `wagyu-zcash-parameters` crate --- librustzcash-wrapper/Cargo.lock | 51 +++++++++++++++++++++++++++++++++ librustzcash-wrapper/Cargo.toml | 1 + librustzcash-wrapper/src/lib.rs | 11 ++++--- src/C/Zcash.chs | 2 -- src/ZcashHaskell/Utils.hs | 6 +--- test/Spec.hs | 5 +++- zcash-haskell.cabal | 1 + 7 files changed, 63 insertions(+), 14 deletions(-) diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index 53583a4..e74b7c7 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -1395,6 +1395,7 @@ dependencies = [ "rand_core", "sapling-crypto", "secp256k1", + "wagyu-zcash-parameters", "zcash_address 0.2.0", "zcash_client_backend", "zcash_note_encryption", @@ -1746,6 +1747,56 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wagyu-zcash-parameters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c904628658374e651288f000934c33ef738b2d8b3e65d4100b70b395dbe2bb" +dependencies = [ + "wagyu-zcash-parameters-1", + "wagyu-zcash-parameters-2", + "wagyu-zcash-parameters-3", + "wagyu-zcash-parameters-4", + "wagyu-zcash-parameters-5", + "wagyu-zcash-parameters-6", +] + +[[package]] +name = "wagyu-zcash-parameters-1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bf2e21bb027d3f8428c60d6a720b54a08bf6ce4e6f834ef8e0d38bb5695da8" + +[[package]] +name = "wagyu-zcash-parameters-2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616ab2e51e74cc48995d476e94de810fb16fc73815f390bf2941b046cc9ba2c" + +[[package]] +name = "wagyu-zcash-parameters-3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14da1e2e958ff93c0830ee68e91884069253bf3462a67831b02b367be75d6147" + +[[package]] +name = "wagyu-zcash-parameters-4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f058aeef03a2070e8666ffb5d1057d8bb10313b204a254a6e6103eb958e9a6d6" + +[[package]] +name = "wagyu-zcash-parameters-5" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffe916b30e608c032ae1b734f02574a3e12ec19ab5cc5562208d679efe4969d" + +[[package]] +name = "wagyu-zcash-parameters-6" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index b8b2b37..abb27b5 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -23,6 +23,7 @@ incrementalmerkletree = "0.5.0" secp256k1 = "0.26.0" jubjub = "0.10.0" rand_core = { version = "0.6.4", features = ["getrandom"]} +wagyu-zcash-parameters = "0.2.0" [features] diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 7397401..be36e38 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -167,6 +167,8 @@ use bech32::{ Bech32m }; +use wagyu_zcash_parameters::load_sapling_parameters; + pub enum RW {} pub const RW: PhantomData = PhantomData; @@ -1637,10 +1639,6 @@ pub extern "C" fn rust_wrapper_create_transaction( o_input_len: usize, out_list: *const u8, out_list_len: usize, - sapspend: *const u8, - sapspend_len: usize, - sapoutput: *const u8, - sapoutput_len: usize, net: bool, bl_height: u32, build: bool, @@ -1796,10 +1794,11 @@ pub extern "C" fn rust_wrapper_create_transaction( } } if build { - let spend_params_in: Vec = marshall_from_haskell_var(sapspend, sapspend_len, RW); + let (spend_params_in, output_params_in) = load_sapling_parameters(); + //let spend_params_in: Vec = marshall_from_haskell_var(sapspend, sapspend_len, RW); let spend_params_reader = Cursor::new(spend_params_in); let spend_prover = SpendParameters::read(spend_params_reader, false).unwrap(); - let output_params_in: Vec = marshall_from_haskell_var(sapoutput, sapoutput_len, RW); + //let output_params_in: Vec = marshall_from_haskell_var(sapoutput, sapoutput_len, RW); let output_params_reader = Cursor::new(output_params_in); let output_prover = OutputParameters::read(output_params_reader, false).unwrap(); let result = if net { diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 0f35be4..bc75ee4 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -283,8 +283,6 @@ import ZcashHaskell.Types , toBorshVar* `[SaplingTxSpend]'& , toBorshVar* `[OrchardTxSpend]'& , toBorshVar* `[OutgoingNote]'& - , toBorshVar* `BS.ByteString'& - , toBorshVar* `BS.ByteString'& , `Bool' , `Word64' , `Bool' diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index 0f67ed7..e7e1aae 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -129,13 +129,11 @@ createTransaction :: -> [SaplingTxSpend] -- ^ the list of Sapling notes to spend -> [OrchardTxSpend] -- ^ the list of Orchard notes to spend -> [OutgoingNote] -- ^ the list of outgoing notes, including change notes - -> SaplingSpendParams -- ^ the Sapling circuit spending parameters - -> SaplingOutputParams -- ^ the Sapling circuit output parameters -> ZcashNet -- ^ the network to be used -> Int -- ^ target block height -> Bool -- ^ True to build, False to estimate fee -> Either TxError HexString -createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing sParams oParams znet bh build = +createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing znet bh build = processResult $! txResult where processResult :: HexString -> Either TxError HexString @@ -168,8 +166,6 @@ createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing sParams oPa sSpend oSpend outgoing - (sapSParams sParams) - (sapOParams oParams) (znet == MainNet) (fromIntegral bh) build diff --git a/test/Spec.hs b/test/Spec.hs index 67ca3ce..ac4581e 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -40,6 +40,7 @@ import GHC.Float.RealFracMethods (properFractionDoubleInteger) import Haskoin.Crypto.Hash (ripemd160) import Haskoin.Crypto.Keys.Extended import Haskoin.Transaction.Common +import Network.HTTP.Simple (Response(..)) import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck @@ -82,6 +83,8 @@ import ZcashHaskell.Types , RawTxOut(..) , RawTxResponse(..) , RawZebraTx(..) + , RpcError(..) + , RpcResponse(..) , SaplingAddress(..) , SaplingBundle(..) , SaplingCommitmentTree(..) @@ -1179,7 +1182,7 @@ main = do updateOrchardWitness wit cmxs `shouldBe` OrchardWitness (hexString - "016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e0002010cfb50d8c877eb39e9c07082a032dd99d34be7c19fa7f30e9fecf5f14736240f019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301") + "016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e01ac20b8170b008888c19fc6e16f5e30a5ef1653e5219d0cd0c9353c3aa8f7982302010cfb50d8c877eb39e9c07082a032dd99d34be7c19fa7f30e9fecf5f14736240f019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301") -- | Properties prop_PhraseLength :: Property diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index b521e58..cd61471 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -86,5 +86,6 @@ test-suite zcash-haskell-test , binary , cryptonite , secp256k1-haskell + , http-conduit pkgconfig-depends: rustzcash_wrapper default-language: Haskell2010 -- 2.34.1 From 1f1ca4e2061891920b84e31996524519ce3ff582 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 17 Sep 2024 13:06:28 -0500 Subject: [PATCH 08/23] docs: update version --- CHANGELOG.md | 4 ++++ zcash-haskell.cabal | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a1a508..91b0b51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.7.0.0] + +- Implement `wagyu-zcash-parameters` in Rust bindings + ## [0.6.2.3] ### Fixed diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index cd61471..2794c6c 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.6.2.3 +version: 0.7.0.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From 343495f6e7439042c5d50c28b0be6a9e67966014 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 18 Sep 2024 07:27:53 -0500 Subject: [PATCH 09/23] feat: add privacy policy error to `TxError` --- CHANGELOG.md | 5 +++++ src/ZcashHaskell/Types.hs | 1 + zcash-haskell.cabal | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91b0b51..9497285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ 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.7.0.1] + +### Added +- New error type `PrivacyPolicyError` + ## [0.7.0.0] - Implement `wagyu-zcash-parameters` in Rust bindings diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 9884b7f..abfb334 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -791,6 +791,7 @@ data TxError | OrchardRecipient | SaplingBuilderNotAvailable | OrchardBuilderNotAvailable + | PrivacyPolicyError !T.Text | ZHError deriving (Eq, Prelude.Show, Read) diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 2794c6c..9cf7abd 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.0.0 +version: 0.7.0.1 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From f24ea80cde27161f56fb972ffd92d276aaa8a735 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Mon, 23 Sep 2024 10:17:35 -0500 Subject: [PATCH 10/23] feat: add block hash to `BlockResponse` --- src/ZcashHaskell/Types.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index abfb334..1647b42 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -256,7 +256,8 @@ instance FromJSON RpcError where -- ** `zcashd` -- | Type to represent response from the `zcashd` RPC `getblock` method data BlockResponse = BlockResponse - { bl_confirmations :: !Integer -- ^ Block confirmations + { bl_hash :: !HexString + , bl_confirmations :: !Integer -- ^ Block confirmations , bl_height :: !Integer -- ^ Block height , bl_time :: !Integer -- ^ Block time , bl_txs :: ![HexString] -- ^ List of transaction IDs in the block @@ -269,7 +270,8 @@ instance FromJSON BlockResponse where h <- obj .: "height" t <- obj .:? "time" txs <- obj .: "tx" - pure $ BlockResponse c h (fromMaybe 0 t) txs + hash <- obj .: "hash" + pure $ BlockResponse hash c h (fromMaybe 0 t) txs -- | Type to represent response from the `zcashd` RPC `getrawtransaction` data RawTxResponse = RawTxResponse -- 2.34.1 From 5de1844e9d19e3b44b649904ba2881cf9843b2e4 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 25 Sep 2024 14:43:11 -0500 Subject: [PATCH 11/23] feat: improve witness updates --- CHANGELOG.md | 6 ++++++ src/ZcashHaskell/Orchard.hs | 10 +++++++--- src/ZcashHaskell/Sapling.hs | 10 +++++++--- zcash-haskell.cabal | 2 +- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9497285..a27c7a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.7.0.2] + +## Changed + +- Modified witness update functions to skip the process if no commitments are present + ## [0.7.0.1] ### Added diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 6bfe0c4..d975c2a 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -229,9 +229,13 @@ getOrchardNotePosition = -- | Update the witness of an Orchard note updateOrchardWitness :: OrchardWitness -> [HexString] -> OrchardWitness updateOrchardWitness wit cmus = - OrchardWitness $ - withPureBorshVarBuffer $ - rustWrapperUpdateOrchardWitness (toBytes $ orchWit wit) (map toBytes cmus) + if not (null cmus) + then OrchardWitness $ + withPureBorshVarBuffer $ + rustWrapperUpdateOrchardWitness + (toBytes $ orchWit wit) + (map toBytes cmus) + else wit -- | Parse a potential Zcash address parseAddress :: BS.ByteString -> Maybe ValidAddress diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index ad7cd01..f145c91 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -218,9 +218,13 @@ getSaplingNotePosition = updateSaplingWitness :: SaplingWitness -> [HexString] -> SaplingWitness updateSaplingWitness wit cmus = - SaplingWitness $ - withPureBorshVarBuffer $ - rustWrapperUpdateSaplingWitness (toBytes $ sapWit wit) (map toBytes cmus) + if not (null cmus) + then SaplingWitness $ + withPureBorshVarBuffer $ + rustWrapperUpdateSaplingWitness + (toBytes $ sapWit wit) + (map toBytes cmus) + else wit -- | Encode a SaplingReceiver into HRF text encodeSaplingAddress :: ZcashNet -> SaplingReceiver -> Maybe T.Text diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 9cf7abd..a3288b5 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.0.1 +version: 0.7.0.2 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From 48afd8159585b29587dbcb5e4d5cf3a3a1b2b820 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Mon, 30 Sep 2024 12:05:00 -0500 Subject: [PATCH 12/23] feat!: migrate Orchard commitment trees to Frontier --- CHANGELOG.md | 12 +- librustzcash-wrapper/Cargo.lock | 286 ++++++++++++-------------------- librustzcash-wrapper/Cargo.toml | 13 +- librustzcash-wrapper/src/lib.rs | 122 +++++++++----- src/C/Zcash.chs | 11 +- src/ZcashHaskell/Orchard.hs | 31 ++-- src/ZcashHaskell/Types.hs | 9 + test/Spec.hs | 41 ++++- zcash-haskell.cabal | 2 +- 9 files changed, 283 insertions(+), 244 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a27c7a7..9a68512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,19 @@ 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.7.1.0] + +### Added + +- Type `OrchardFrontier` + +### Changed + +- Modified Orchard commitment trees functions to use Frontier + ## [0.7.0.2] -## Changed +### Changed - Modified witness update functions to skip the process if no commitments are present diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index e74b7c7..604eeee 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -66,9 +66,9 @@ checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" -version = "1.0.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "bip0039" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef0f0152ec5cf17f49a5866afaa3439816207fd4f0a224c0211ffaf5e278426" +checksum = "568b6890865156d9043af490d4c4081c385dd68ea10acd6ca15733d511e6b51c" dependencies = [ "hmac", "pbkdf2", @@ -132,6 +132,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bip32" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" +dependencies = [ + "bs58 0.5.0", + "hmac", + "rand_core", + "ripemd", + "secp256k1", + "sha2 0.10.6", + "subtle", + "zeroize", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -318,12 +334,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "bumpalo" -version = "3.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" - [[package]] name = "byteorder" version = "1.4.3" @@ -709,19 +719,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "hdwallet" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a03ba7d4c9ea41552cd4351965ff96883e629693ae85005c501bb4b9e1c48a7" -dependencies = [ - "lazy_static", - "rand_core", - "ring", - "secp256k1", - "thiserror", -] - [[package]] name = "heck" version = "0.4.1" @@ -760,9 +757,9 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1872810fb725b06b8c153dde9e86f3ec26747b9b60096da7a869883b549cbe" +checksum = "75346da3bd8e3d8891d02508245ed2df34447ca6637e343829f8d08986e9cde2" dependencies = [ "either", ] @@ -815,15 +812,6 @@ dependencies = [ "either", ] -[[package]] -name = "js-sys" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "jubjub" version = "0.10.0" @@ -999,9 +987,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb255c3ffdccd3c84fe9ebed72aef64fdc72e6a3e4180dd411002d47abaad42" +checksum = "4dc7bde644aeb980be296cd908c6650894dc8541deb56f9f5294c52ed7ca568f" dependencies = [ "aes", "bitvec", @@ -1022,6 +1010,7 @@ dependencies = [ "serde", "subtle", "tracing", + "visibility", "zcash_note_encryption", "zcash_spec", "zip32", @@ -1038,9 +1027,9 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core", @@ -1064,9 +1053,9 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.10.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", "password-hash", @@ -1147,9 +1136,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", "prost-derive", @@ -1157,9 +1146,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", "heck", @@ -1174,14 +1163,13 @@ dependencies = [ "regex", "syn 2.0.32", "tempfile", - "which", ] [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools", @@ -1192,9 +1180,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ "prost", ] @@ -1341,21 +1329,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - [[package]] name = "ripemd" version = "0.1.3" @@ -1384,6 +1357,7 @@ name = "rustzcash-wrapper" version = "0.1.0" dependencies = [ "bech32 0.11.0", + "bip0039", "borsh 0.10.3", "f4jumble", "haskell-ffi", @@ -1405,9 +1379,9 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f4270033afcb0c74c5c7d59c73cfd1040367f67f224fe7ed9a919ae618f1b7" +checksum = "15e379398fffad84e49f9a45a05635fc004f66086e65942dbf4eb95332c26d2a" dependencies = [ "aes", "bellman", @@ -1443,9 +1417,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "secp256k1" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ "secp256k1-sys", ] @@ -1514,9 +1488,9 @@ dependencies = [ [[package]] name = "shardtree" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf20c7a2747d9083092e3a3eeb9a7ed75577ae364896bebbc5e0bdcd4e97735" +checksum = "78222845cd8bbe5eb95687407648ff17693a35de5e8abaa39a4681fb21e033f9" dependencies = [ "bitflags 2.4.2", "either", @@ -1649,13 +1623,14 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ "prettyplease", "proc-macro2", "prost-build", + "prost-types", "quote", "syn 2.0.32", ] @@ -1718,9 +1693,9 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -1735,18 +1710,23 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + [[package]] name = "wagyu-zcash-parameters" version = "0.2.0" @@ -1803,70 +1783,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.32", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "web-sys" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "which" version = "4.4.0" @@ -1878,28 +1794,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.48.0" @@ -1989,21 +1883,22 @@ dependencies = [ [[package]] name = "zcash_address" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bce173f1d9ed4f806e310bc3a873301531e7a6dc209928584d6404e3f8228ef4" +checksum = "a6d26f21381dc220836dd8d2a9a10dbe85928a26232b011bc6a42b611789b743" dependencies = [ "bech32 0.9.1", "bs58 0.5.0", "f4jumble", "zcash_encoding", + "zcash_protocol", ] [[package]] name = "zcash_client_backend" -version = "0.11.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001ec65dc2828ee648dc6d29f0944d7a877fe68ad06e001a203c11770ab1b3d4" +checksum = "80e3a0f3e5d7f299d8b7ef3237697630989c31ab1b162824c99c1cd8bc83715e" dependencies = [ "base64", "bech32 0.9.1", @@ -2029,19 +1924,21 @@ dependencies = [ "tonic-build", "tracing", "which", - "zcash_address 0.3.1", + "zcash_address 0.4.0", "zcash_encoding", "zcash_keys", "zcash_note_encryption", "zcash_primitives", + "zcash_protocol", "zip32", + "zip321", ] [[package]] name = "zcash_encoding" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03391b81727875efa6ac0661a20883022b6fba92365dc121c48fa9b00c5aac0" +checksum = "052d8230202f0a018cd9b5d1b56b94cd25e18eccc2d8665073bcea8261ab87fc" dependencies = [ "byteorder", "nonempty", @@ -2049,11 +1946,12 @@ dependencies = [ [[package]] name = "zcash_keys" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f22d3407fdd6992b49f037f23862ab376be6013be6f2d0bc85948a635edc1f5" +checksum = "712faf4070107ab0b2828d0eda6aeaf4c3cb02564109832d95b97ad3467c95a5" dependencies = [ "bech32 0.9.1", + "blake2b_simd", "bls12_381", "bs58 0.5.0", "document-features", @@ -2062,11 +1960,13 @@ dependencies = [ "nonempty", "rand_core", "sapling-crypto", + "secrecy", "subtle", "tracing", - "zcash_address 0.3.1", + "zcash_address 0.4.0", "zcash_encoding", "zcash_primitives", + "zcash_protocol", "zip32", ] @@ -2085,20 +1985,20 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9070e084570bb78aed4f8d71fd6254492e62c87a5d01e084183980e98117092d" +checksum = "5f044bc9cf2887ec408196fbafb44749e5581f57cc18d8da7aabaeb60cc40c64" dependencies = [ "aes", - "bip0039", + "bip32", "blake2b_simd", + "bs58 0.5.0", "byteorder", "document-features", "equihash", "ff", "fpe", "group", - "hdwallet", "hex", "incrementalmerkletree", "jubjub", @@ -2114,13 +2014,24 @@ dependencies = [ "sha2 0.10.6", "subtle", "tracing", - "zcash_address 0.3.1", + "zcash_address 0.4.0", "zcash_encoding", "zcash_note_encryption", + "zcash_protocol", "zcash_spec", "zip32", ] +[[package]] +name = "zcash_protocol" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f35eac659fdbba614333d119217c5963c0d7cea43aee33176c4f2f95e5460d8d" +dependencies = [ + "document-features", + "memuse", +] + [[package]] name = "zcash_spec" version = "0.1.0" @@ -2160,3 +2071,16 @@ dependencies = [ "memuse", "subtle", ] + +[[package]] +name = "zip321" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc85f862f7be64fb0d46f9eb5b82ad54e58cde314fa979d5bae591bc0143693" +dependencies = [ + "base64", + "nom", + "percent-encoding", + "zcash_address 0.4.0", + "zcash_protocol", +] diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index abb27b5..208022f 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -11,19 +11,20 @@ f4jumble = "0.1" zcash_address = "0.2.0" borsh = "0.10" bech32 = "0.11" -orchard = "0.7.1" +orchard = "0.9.0" zcash_note_encryption = "0.4.0" -zcash_primitives = { version = "0.14.0", features = ["transparent-inputs"]} -zcash_client_backend = "0.11.1" -sapling-crypto = "0.1.3" +zcash_primitives = { version = "0.16.0", features = ["transparent-inputs"]} +zcash_client_backend = "0.13.0" +sapling-crypto = "0.2" zip32 = "0.1.0" proc-macro2 = "1.0.66" nonempty = "0.7.0" -incrementalmerkletree = "0.5.0" -secp256k1 = "0.26.0" +incrementalmerkletree = "0.6.0" +secp256k1 = "0.27.0" jubjub = "0.10.0" rand_core = { version = "0.6.4", features = ["getrandom"]} wagyu-zcash-parameters = "0.2.0" +bip0039 = "0.12.0" [features] diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index be36e38..63c4d2b 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -30,7 +30,12 @@ use secp256k1::SecretKey; use jubjub::Fr; use incrementalmerkletree::{ - frontier::CommitmentTree, + Position, + frontier::{ + CommitmentTree, + Frontier, + NonEmptyFrontier + }, witness::IncrementalWitness }; @@ -79,18 +84,20 @@ use sapling_crypto::{ } }; +use bip0039::{Count, Mnemonic, English}; + use zcash_primitives::{ merkle_tree::{ read_commitment_tree, write_commitment_tree, read_incremental_witness, - write_incremental_witness + write_incremental_witness, + read_frontier_v1 }, legacy::{ Script, TransparentAddress }, - zip339::{Count, Mnemonic}, transaction::{ Transaction, fees::zip317::FeeRule, @@ -147,7 +154,7 @@ use orchard::{ }, Action, keys::{SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope}, - note::{RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, + note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, note_encryption::OrchardDomain, primitives::redpallas::{VerificationKey, SpendAuth, Signature}, tree::{ @@ -686,6 +693,27 @@ impl ToHaskell for Hsvk { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Hfrontier { + position: u64, + leaf: Hhex, + ommers: Vec> +} + +impl ToHaskell for Hfrontier { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl FromHaskell for Hfrontier { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Hfrontier::deserialize(buf)?; + Ok(x) + } +} + 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())) } @@ -1026,7 +1054,7 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( 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_nullifier(*action.nullifier()); + let domain = OrchardDomain::for_action(&action); let dec_fvk = FullViewingKey::from_bytes(&fvk_array); match dec_fvk { Some(fvk) => { @@ -1073,7 +1101,7 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt_sk( ValueCommitment::from_bytes(&to_array(note_input.cv.bytes)).unwrap(), Signature::from(to_array(note_input.auth.bytes))); let sk_array = to_array(sk_input); - let domain = OrchardDomain::for_nullifier(*action.nullifier()); + let domain = OrchardDomain::for_action(&action); let dec_sk = SpendingKey::from_bytes(sk_array).unwrap(); let fvk = FullViewingKey::from(&dec_sk); let ivk = if external { @@ -1186,7 +1214,7 @@ pub extern "C" fn rust_wrapper_gen_seed_phrase( out: *mut u8, out_len: &mut usize ){ - let mnemonic = Mnemonic::generate(Count::Words24); + let mnemonic: Mnemonic = Mnemonic::generate(Count::Words24); let seed = mnemonic.phrase().as_bytes().to_vec(); marshall_to_haskell_var(&seed, out, out_len, RW); } @@ -1199,7 +1227,7 @@ pub extern "C" fn rust_wrapper_recover_seed( out_len: &mut usize ){ let phrase: String = marshall_from_haskell_var(input, input_len, RW); - let mnemonic = Mnemonic::from_phrase(phrase); + let mnemonic = >::from_phrase(phrase); match mnemonic { Ok(m) => { let s = m.to_seed("").to_vec(); @@ -1456,6 +1484,34 @@ pub extern "C" fn rust_wrapper_update_sapling_witness( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_frontier( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + //let comm_tree: Frontier = read_frontier_v1(tree_reader).unwrap(); + let frontier: Frontier = comm_tree.to_frontier(); + match frontier.value() { + Some(f1) => { + let (pos, leaf, omm) = f1.clone().into_parts(); + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; + marshall_to_haskell_var(&f, out, out_len, RW); + }, + None => { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + marshall_to_haskell_var(&f0, out, out_len, RW); + } + } +} + + + + #[no_mangle] pub extern "C" fn rust_wrapper_read_orchard_commitment_tree( tree: *const u8, @@ -1465,37 +1521,23 @@ pub extern "C" fn rust_wrapper_read_orchard_commitment_tree( out: *mut u8, out_len: &mut usize ){ - let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); - let tree_reader = Cursor::new(tree_in); - let ct = read_commitment_tree::>, 32>(tree_reader); - match ct { - Ok(mut comm_tree) => { - let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); - let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(node_in)); - if orchard_note_comm.is_some().into() { - let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); - comm_tree.append(n); - let mut out_bytes: Vec = Vec::new(); - let result = write_commitment_tree(&comm_tree, &mut out_bytes ); - match result { - Ok(()) => { - let h = Hhex { bytes: out_bytes}; - marshall_to_haskell_var(&h, out, out_len, RW); - }, - Err(_e) => { - let h0 = Hhex { bytes: vec![0]}; - marshall_to_haskell_var(&h0, out, out_len, RW); - } - } - } else { - let h0 = Hhex { bytes: vec![0]}; - marshall_to_haskell_var(&h0, out, out_len, RW); - } - }, - Err(_e) => { - let h0 = Hhex { bytes: vec![0]}; - marshall_to_haskell_var(&h0, out, out_len, RW); - } + let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); + //let tree_reader = Cursor::new(tree_in); + //let mut comm_tree: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + //let mut comm_tree: Frontier = read_frontier_v1(tree_reader).unwrap(); + let leaf = MerkleHashOrchard::from_bytes(&to_array(tree_in.leaf.bytes)).unwrap(); + let mut comm_tree: NonEmptyFrontier = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.clone())).unwrap() ).collect()).unwrap(); + let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); + let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(node_in)); + if orchard_note_comm.is_some().into() { + let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); + comm_tree.append(n); + let (pos, leaf, omm) = comm_tree.into_parts(); + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; + marshall_to_haskell_var(&f, out, out_len, RW); + } else { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + marshall_to_haskell_var(&f0, out, out_len, RW); } } @@ -1726,7 +1768,7 @@ pub extern "C" fn rust_wrapper_create_transaction( if o_in.sk.len() > 1 { let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap(); let pay_addr = OrchardAddress::from_raw_address_bytes(&to_array(o_in.note.recipient)).unwrap(); - let rho = Nullifier::from_bytes(&to_array(o_in.note.rho)).unwrap(); + let rho = Rho::from_bytes(&to_array(o_in.note.rho)).unwrap(); let rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap(); let val = NoteValue::from_raw(o_in.note.note); let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap(); diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index bc75ee4..884d0e6 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -240,9 +240,16 @@ import ZcashHaskell.Types #} {# fun unsafe rust_wrapper_read_orchard_commitment_tree as rustWrapperReadOrchardCommitmentTree - { toBorshVar* `BS.ByteString'& + { toBorshVar* `OrchardFrontier'& , toBorshVar* `BS.ByteString'& - , getVarBuffer `Buffer HexString'& + , getVarBuffer `Buffer OrchardFrontier'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_orchard_frontier as rustWrapperReadOrchardFrontier + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer OrchardFrontier'& } -> `()' #} diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index d975c2a..a4ef75a 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -24,6 +24,7 @@ import C.Zcash , rustWrapperOrchardNoteDecode , rustWrapperOrchardNoteDecodeSK , rustWrapperReadOrchardCommitmentTree + , rustWrapperReadOrchardFrontier , rustWrapperReadOrchardPosition , rustWrapperReadOrchardWitness , rustWrapperUADecode @@ -194,21 +195,29 @@ decryptOrchardActionSK sk scope oa = withPureBorshVarBuffer $ rustWrapperOrchardNoteDecodeSK (getBytes sk) oa (scope == External) --- | Update a Orchard commitment tree -updateOrchardCommitmentTree :: - OrchardCommitmentTree -- ^ the base tree - -> HexString -- ^ the new note commitment - -> Maybe OrchardCommitmentTree -updateOrchardCommitmentTree tree cmx = - if BS.length (hexBytes updatedTree) > 1 - then Just $ OrchardCommitmentTree updatedTree +getOrchardFrontier :: OrchardCommitmentTree -> Maybe OrchardFrontier +getOrchardFrontier tree = + if of_pos updatedTree > 1 + then Just updatedTree else Nothing where updatedTree = withPureBorshVarBuffer $ - rustWrapperReadOrchardCommitmentTree - (hexBytes $ orchTree tree) - (hexBytes cmx) + rustWrapperReadOrchardFrontier $ toBytes $ orchTree tree + +-- | Update a Orchard commitment tree +updateOrchardCommitmentTree :: + OrchardFrontier -- ^ the base tree + -> HexString -- ^ the new note commitment + -> Maybe OrchardFrontier +updateOrchardCommitmentTree tree cmx = + if of_pos updatedTree > 1 + then Just updatedTree + else Nothing + where + updatedTree = + withPureBorshVarBuffer $ + rustWrapperReadOrchardCommitmentTree tree (hexBytes cmx) -- | Get the Orchard incremental witness from a commitment tree getOrchardWitness :: OrchardCommitmentTree -> Maybe OrchardWitness diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 1647b42..0fc44c3 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -708,6 +708,15 @@ newtype OrchardCommitmentTree = OrchardCommitmentTree { orchTree :: HexString } deriving (Eq, Prelude.Show, Read) +data OrchardFrontier = OrchardFrontier + { of_pos :: !Int64 + , of_leaf :: !HexString + , of_ommers :: ![BS.ByteString] + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardFrontier + -- | Type for a Sapling incremental witness newtype OrchardWitness = OrchardWitness { orchWit :: HexString diff --git a/test/Spec.hs b/test/Spec.hs index ac4581e..ac744cb 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -922,7 +922,7 @@ main = do getSaplingNotePosition <$> (getSaplingWitness =<< updateSaplingCommitmentTree tree cmu1) p `shouldBe` Just 129405 - describe "Orchard commitment trees" $ do + {- describe "Orchard commitment trees" $ do let tree = OrchardCommitmentTree $ hexString @@ -942,7 +942,7 @@ main = do let p = getOrchardNotePosition <$> (getOrchardWitness =<< updateOrchardCommitmentTree tree cmx) - p `shouldBe` Just 39432 + p `shouldBe` Just 39432 -} describe "Extract Sapling Address - UA Valid" $ do let sr = getSaplingFromUA @@ -1148,6 +1148,43 @@ main = do Just addr -> do let eadr = decodeExchangeAddress (E.encodeUtf8 addr) eadr `shouldNotBe` Nothing + describe "Tree updates" $ do + it "Orchard" $ do + let tree = + OrchardCommitmentTree $ + hexString + "0136a7886d7d73bc1845223165fd9cb0cef02046c707e8f88a7f61564720bd0f3501dca1fbdd7b5ba92a0809af5e85874626ce0db14d0532a48e41dde6f0f81b46011f0001fb48c27bd07e68f27aba47cd6e93fa961e0ef8c63f993963a614e56855d2013c0001ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" + let cmx1 = + hexString + "1712ead46028d4349e234abf59e94e0640fe7a0829e2e2e17e1a931631810400" + let cmx2 = + hexString + "39f5ad39817fb432fa07c5feb3a957189fbe7662a4b5555ca95093b6d853cf07" + let cmx3 = + hexString + "84f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f" + let cmx4 = + hexString + "e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d429" + let finalTree = + getOrchardFrontier $ + OrchardCommitmentTree $ + hexString + "0184f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f01e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d4291f0000014b1a76d3820087b26cd087ca84e17f3067a25ebed82ad23a93fa485affb5530b01ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" + case getOrchardFrontier tree of + Nothing -> assertFailure "Failed to get frontier" + Just t1 -> + case updateOrchardCommitmentTree t1 cmx1 of + Nothing -> assertFailure "Failed to update frontier with cmx" + Just t2 -> + case updateOrchardCommitmentTree t2 cmx2 of + Nothing -> assertFailure "Failed to update frontier with cmx" + Just t3 -> + case updateOrchardCommitmentTree t3 cmx3 of + Nothing -> + assertFailure "Failed to update frontier with cmx" + Just t4 -> + updateOrchardCommitmentTree t4 cmx4 `shouldBe` finalTree describe "Witness updates" $ do it "Sapling" $ do let wit = diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index a3288b5..bcec9de 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.0.2 +version: 0.7.1.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From 38e4131daaebd45bfe005e753b0b5716a4481d89 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Mon, 30 Sep 2024 13:18:47 -0500 Subject: [PATCH 13/23] feat!: update Orchard witness calculation Now uses Frontier --- librustzcash-wrapper/src/lib.rs | 7 ++++--- src/C/Zcash.chs | 2 +- src/ZcashHaskell/Orchard.hs | 6 ++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 63c4d2b..6034424 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -1548,9 +1548,10 @@ pub extern "C" fn rust_wrapper_read_orchard_witness( out: *mut u8, out_len: &mut usize ){ - let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); - let tree_reader = Cursor::new(tree_in); - let ct: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); + let leaf = MerkleHashOrchard::from_bytes(&to_array(tree_in.leaf.bytes)).unwrap(); + let frontier: Frontier = Frontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.clone())).unwrap() ).collect()).unwrap(); + let ct: CommitmentTree = CommitmentTree::from_frontier(&frontier); let inc_wit = IncrementalWitness::from_tree(ct); let mut out_bytes: Vec = Vec::new(); let result = write_incremental_witness(&inc_wit, &mut out_bytes); diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 884d0e6..beb11e2 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -255,7 +255,7 @@ import ZcashHaskell.Types #} {# fun unsafe rust_wrapper_read_orchard_witness as rustWrapperReadOrchardWitness - { toBorshVar* `BS.ByteString'& + { toBorshVar* `OrchardFrontier'& , getVarBuffer `Buffer HexString'& } -> `()' diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index a4ef75a..1bb7167 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -220,15 +220,13 @@ updateOrchardCommitmentTree tree cmx = rustWrapperReadOrchardCommitmentTree tree (hexBytes cmx) -- | Get the Orchard incremental witness from a commitment tree -getOrchardWitness :: OrchardCommitmentTree -> Maybe OrchardWitness +getOrchardWitness :: OrchardFrontier -> Maybe OrchardWitness getOrchardWitness tree = if BS.length (hexBytes wit) > 1 then Just $ OrchardWitness wit else Nothing where - wit = - withPureBorshVarBuffer $ - rustWrapperReadOrchardWitness (hexBytes $ orchTree tree) + wit = withPureBorshVarBuffer $ rustWrapperReadOrchardWitness tree -- | Get the Sapling note position from a witness getOrchardNotePosition :: OrchardWitness -> Integer -- 2.34.1 From 3969490283420af0bb193590a1e5d4b6828cc2f9 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Mon, 30 Sep 2024 14:50:54 -0500 Subject: [PATCH 14/23] feat: Use the notes to determine anchors --- librustzcash-wrapper/src/lib.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 6034424..10e8df0 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -1702,14 +1702,24 @@ pub extern "C" fn rust_wrapper_create_transaction( let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); let orch_wit_reader = Cursor::new(orch_wit_in); let orch_iw = read_commitment_tree::>, 32>(orch_wit_reader); - let orch_anchor = match orch_iw { - Ok(o_iw) => { - Some(OrchardAnchor::from(o_iw.root())) - }, - Err(_e) => { + let orch_input: Vec = marshall_from_haskell_var(o_input, o_input_len, RW); + //let orch_anchor = match orch_iw { + //Ok(o_iw) => { + //Some(OrchardAnchor::from(o_iw.root())) + //}, + //Err(_e) => { + //None + //} + //}; + let orch_anchor = + if orch_input.is_empty() { None - } - }; + } else { + let oi = &orch_input[0]; + let wit_reader = Cursor::new(&oi.iw); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + Some(OrchardAnchor::from(iw.root())) + }; let build_config = BuildConfig::Standard {sapling_anchor: sap_anchor, orchard_anchor: orch_anchor}; let mut main_builder = Builder::new(MainNetwork, BlockHeight::from(bl_height), build_config); let mut test_builder = Builder::new(TestNetwork, BlockHeight::from(bl_height), build_config); @@ -1764,7 +1774,6 @@ pub extern "C" fn rust_wrapper_create_transaction( } } } - let orch_input: Vec = marshall_from_haskell_var(o_input, o_input_len, RW); for o_in in orch_input { if o_in.sk.len() > 1 { let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap(); -- 2.34.1 From 5a08c802854eebd1b5b4d8e7e1e2dad574b06100 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 11 Oct 2024 08:17:02 -0500 Subject: [PATCH 15/23] feat: add `toJSON` for `BlockResponse` --- CHANGELOG.md | 21 +++++++++++++++++++++ cabal.project.freeze | 23 ++++++++++++----------- src/ZcashHaskell/Types.hs | 10 ++++++++++ zcash-haskell.cabal | 2 +- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a68512..1feacf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ 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.7.1.1] + +### Added + +- `ToJSON` instance for `BlockResponse` + +### Changed + +- Updated libraries: + - conduit + - data-fix + - happy + - happy-lib + - http-conduit + - iproute + - mono-traversable + - network + - secp256k1-haskell + - strict + - typed-process + ## [0.7.1.0] ### Added diff --git a/cabal.project.freeze b/cabal.project.freeze index b5bd80f..206c446 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -54,7 +54,7 @@ constraints: any.Cabal ==3.10.3.0, any.colour ==2.3.6, any.comonad ==5.0.8, comonad +containers +distributive +indexed-traversable, - any.conduit ==1.3.5, + any.conduit ==1.3.6, any.conduit-extra ==1.3.6, any.containers ==0.6.7, any.contravariant ==1.5.5, @@ -74,7 +74,7 @@ constraints: any.Cabal ==3.10.3.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-fix ==0.3.3, + any.data-fix ==0.3.4, any.deepseq ==1.4.8.1, any.directory ==1.3.8.4, any.distributive ==0.6.2.1, @@ -93,7 +93,8 @@ constraints: any.Cabal ==3.10.3.0, any.ghc-boot-th ==9.6.5, any.ghc-prim ==0.10.0, any.half ==0.3.1, - any.happy ==1.20.1.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, @@ -109,7 +110,7 @@ constraints: any.Cabal ==3.10.3.0, any.http-client ==0.7.17, http-client +network-uri, any.http-client-tls ==0.3.6.3, - any.http-conduit ==2.3.8.3, + any.http-conduit ==2.3.9, http-conduit +aeson, any.http-types ==0.12.4, any.indexed-traversable ==0.1.4, @@ -118,16 +119,16 @@ constraints: any.Cabal ==3.10.3.0, any.integer-gmp ==1.1, any.integer-logarithms ==1.0.3.1, integer-logarithms -check-bounds +integer-gmp, - any.iproute ==1.7.12, + any.iproute ==1.7.14, any.language-c ==0.9.3, language-c -allwarnings +iecfpextension +usebytestrings, any.memory ==0.18.0, memory +support_bytestring +support_deepseq, any.mime-types ==0.1.2.0, - any.mono-traversable ==1.0.17.0, + any.mono-traversable ==1.0.20.0, any.mtl ==2.3.1, any.murmur3 ==1.0.5, - any.network ==3.2.1.0, + any.network ==3.2.4.0, network -devel, any.network-uri ==2.6.4.2, any.old-locale ==1.0.0.7, @@ -150,7 +151,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.2.0, + any.secp256k1-haskell ==1.4.0, any.semialign ==1.3.1, semialign +semigroupoids, any.semigroupoids ==6.0.1, @@ -165,7 +166,7 @@ constraints: any.Cabal ==3.10.3.0, any.stm ==2.5.1.0, any.streaming-commons ==0.2.2.6, streaming-commons -use-bytestring-builder, - any.strict ==0.5, + any.strict ==0.5.1, any.string-conversions ==0.4.0.1, any.tagged ==0.8.8, tagged +deepseq +transformers, @@ -185,7 +186,7 @@ constraints: any.Cabal ==3.10.3.0, 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.11.1, + any.typed-process ==0.2.12.0, any.unix ==2.8.4.0, any.unix-time ==0.4.15, any.unliftio-core ==0.2.1.0, @@ -204,4 +205,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-07-01T20:28:56Z +index-state: hackage.haskell.org 2024-10-11T12:55:31Z diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 0fc44c3..ebf3dbf 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -273,6 +273,16 @@ instance FromJSON BlockResponse where hash <- obj .: "hash" pure $ BlockResponse hash c h (fromMaybe 0 t) txs +instance ToJSON BlockResponse where + toJSON (BlockResponse h c ht t txs) = + object + [ "hash" .= h + , "confirmations" .= c + , "height" .= ht + , "time" .= t + , "tx" .= txs + ] + -- | Type to represent response from the `zcashd` RPC `getrawtransaction` data RawTxResponse = RawTxResponse { rt_id :: !HexString diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index bcec9de..babe1f3 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.1.0 +version: 0.7.1.1 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From 3203c7e8ffefcb788caf5801425af344a85922bc Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 18 Oct 2024 08:47:52 -0500 Subject: [PATCH 16/23] feat: implement Frontier for Sapling witnesses --- librustzcash-wrapper/src/lib.rs | 109 ++++++++++++++++++++++++++------ src/C/Zcash.chs | 11 +++- src/ZcashHaskell/Sapling.hs | 31 +++++---- src/ZcashHaskell/Types.hs | 9 +++ 4 files changed, 128 insertions(+), 32 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 10e8df0..a0e98b6 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -1369,6 +1369,31 @@ pub extern "C" fn rust_wrapper_bech32_encode( marshall_to_haskell_var(&string, out, out_len, RW); } +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_frontier( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + //let comm_tree: Frontier = read_frontier_v1(tree_reader).unwrap(); + let frontier: Frontier = comm_tree.to_frontier(); + match frontier.value() { + Some(f1) => { + let (pos, leaf, omm) = f1.clone().into_parts(); + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; + marshall_to_haskell_var(&f, out, out_len, RW); + }, + None => { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + marshall_to_haskell_var(&f0, out, out_len, RW); + } + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_read_sapling_commitment_tree( tree: *const u8, @@ -1687,21 +1712,31 @@ pub extern "C" fn rust_wrapper_create_transaction( build: bool, out: *mut u8, out_len: &mut usize){ - let sap_wit_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); - let sap_wit_reader = Cursor::new(sap_wit_in); - let sap_iw = read_commitment_tree::>, SAPLING_DEPTH>(sap_wit_reader); - let sap_anchor = match sap_iw { - Ok(s_iw) => { - Some(SaplingAnchor::from(s_iw.root())) - }, - Err(_e) => { + //let sap_wit_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); + //let sap_wit_reader = Cursor::new(sap_wit_in); + //let sap_iw = read_commitment_tree::>, SAPLING_DEPTH>(sap_wit_reader); + let sap_input: Vec = marshall_from_haskell_var(s_input, s_input_len, RW); + let sap_anchor = + if sap_input.is_empty() { None - } - }; + } else { + let si = &sap_input[0]; + let swit_reader = Cursor::new(&si.iw); + let iw: IncrementalWitness = read_incremental_witness(swit_reader).unwrap(); + Some(SaplingAnchor::from(iw.root())) + }; + //let sap_anchor = match sap_iw { + //Ok(s_iw) => { + //Some(SaplingAnchor::from(s_iw.root())) + //}, + //Err(_e) => { + //None + //} + //}; //println!("{:?}", sap_anchor); - let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); - let orch_wit_reader = Cursor::new(orch_wit_in); - let orch_iw = read_commitment_tree::>, 32>(orch_wit_reader); + //let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); + //let orch_wit_reader = Cursor::new(orch_wit_in); + //let orch_iw = read_commitment_tree::>, 32>(orch_wit_reader); let orch_input: Vec = marshall_from_haskell_var(o_input, o_input_len, RW); //let orch_anchor = match orch_iw { //Ok(o_iw) => { @@ -1746,7 +1781,6 @@ pub extern "C" fn rust_wrapper_create_transaction( } } } - let sap_input: Vec = marshall_from_haskell_var(s_input, s_input_len, RW); for s_in in sap_input { if s_in.sk.len() > 1 { let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk); @@ -1763,13 +1797,32 @@ pub extern "C" fn rust_wrapper_create_transaction( let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); let merkle_path = iw.path().unwrap(); if net { - let _mb = main_builder.add_sapling_spend::(&sk, note, merkle_path).unwrap(); + let mb = main_builder.add_sapling_spend::(&sk, note, merkle_path); + match mb { + Ok(()) => { + continue; + }, + Err(_e) => { + let x = Hhex {bytes: vec![5]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } else { - let _tb = test_builder.add_sapling_spend::(&sk, note, merkle_path).unwrap(); + let tb = test_builder.add_sapling_spend::(&sk, note, merkle_path); + match tb { + Ok(()) => { + continue; + }, + Err(_e) => { + let x = Hhex {bytes: vec![5]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } }, Err(_e) => { - continue; + let x = Hhex {bytes: vec![5]}; + marshall_to_haskell_var(&x, out, out_len, RW); } } } @@ -1786,9 +1839,27 @@ pub extern "C" fn rust_wrapper_create_transaction( let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); let merkle_path = OrchardMerklePath::from(iw.path().unwrap()); if net { - let _mb = main_builder.add_orchard_spend::(&sp_key, note, merkle_path).unwrap(); + let mb = main_builder.add_orchard_spend::(&sp_key, note, merkle_path); + match mb { + Ok(()) => { + continue; + }, + Err(_e) => { + let x = Hhex {bytes: vec![7]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } else { - let _tb = test_builder.add_orchard_spend::(&sp_key, note, merkle_path).unwrap(); + let tb = test_builder.add_orchard_spend::(&sp_key, note, merkle_path); + match tb { + Ok(()) => { + continue; + }, + Err(_e) => { + let x = Hhex {bytes: vec![7]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } } } diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index beb11e2..ab4bd54 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -204,9 +204,9 @@ import ZcashHaskell.Types #} {# fun unsafe rust_wrapper_read_sapling_commitment_tree as rustWrapperReadSaplingCommitmentTree - { toBorshVar* `BS.ByteString'& + { toBorshVar* `SaplingFrontier'& , toBorshVar* `BS.ByteString'& - , getVarBuffer `Buffer HexString'& + , getVarBuffer `Buffer SaplingFrontier'& } -> `()' #} @@ -232,6 +232,13 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_sapling_frontier as rustWrapperReadSaplingFrontier + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer SaplingFrontier'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_decode_sapling_address as rustWrapperDecodeSaplingAddress { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer (BS.ByteString)'& diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index f145c91..4b7c286 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -21,6 +21,7 @@ import C.Zcash ( rustWrapperDecodeSaplingAddress , rustWrapperIsShielded , rustWrapperReadSaplingCommitmentTree + , rustWrapperReadSaplingFrontier , rustWrapperReadSaplingPosition , rustWrapperReadSaplingWitness , rustWrapperSaplingCheck @@ -184,21 +185,29 @@ genSaplingInternalAddress sk = res = withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk) --- | Update a Sapling commitment tree -updateSaplingCommitmentTree :: - SaplingCommitmentTree -- ^ the base tree - -> HexString -- ^ the new note commitment - -> Maybe SaplingCommitmentTree -updateSaplingCommitmentTree tree cmu = - if BS.length (hexBytes updatedTree) > 1 - then Just $ SaplingCommitmentTree updatedTree +getSaplingFrontier :: SaplingCommitmentTree -> Maybe SaplingFrontier +getSaplingFrontier tree = + if sf_pos updatedTree > 1 + then Just updatedTree else Nothing where updatedTree = withPureBorshVarBuffer $ - rustWrapperReadSaplingCommitmentTree - (hexBytes $ sapTree tree) - (hexBytes cmu) + rustWrapperReadSaplingFrontier $ toBytes $ sapTree tree + +-- | Update a Sapling commitment tree +updateSaplingCommitmentTree :: + SaplingFrontier -- ^ the base tree + -> HexString -- ^ the new note commitment + -> Maybe SaplingFrontier +updateSaplingCommitmentTree tree cmu = + if sf_pos updatedTree > 1 + then Just updatedTree + else Nothing + where + updatedTree = + withPureBorshVarBuffer $ + rustWrapperReadSaplingCommitmentTree tree (hexBytes cmu) -- | Get the Sapling incremental witness from a commitment tree getSaplingWitness :: SaplingCommitmentTree -> Maybe SaplingWitness diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index ebf3dbf..8105d30 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -611,6 +611,15 @@ newtype SaplingCommitmentTree = SaplingCommitmentTree { sapTree :: HexString } deriving (Eq, Prelude.Show, Read) +data SaplingFrontier = SaplingFrontier + { sf_pos :: !Int64 + , sf_leaf :: !HexString + , sf_ommers :: ![BS.ByteString] + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct SaplingFrontier + -- | Type for a Sapling incremental witness newtype SaplingWitness = SaplingWitness { sapWit :: HexString -- 2.34.1 From 96cfe3a52a0fc2c6910ea193daa51dc839857f0a Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 18 Oct 2024 09:43:02 -0500 Subject: [PATCH 17/23] feat: use `SaplingFrontier` for witnesses --- librustzcash-wrapper/src/lib.rs | 7 ++++--- src/C/Zcash.chs | 2 +- src/ZcashHaskell/Sapling.hs | 6 ++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index a0e98b6..3209737 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -1445,9 +1445,10 @@ pub extern "C" fn rust_wrapper_read_sapling_witness( out: *mut u8, out_len: &mut usize ){ - let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); - let tree_reader = Cursor::new(tree_in); - let ct: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); + let leaf = Node::from_bytes(to_array(tree_in.leaf.bytes)).unwrap(); + let frontier: Frontier = Frontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| Node::from_bytes(to_array(x.clone())).unwrap() ).collect()).unwrap(); + let ct: CommitmentTree = CommitmentTree::from_frontier(&frontier); let inc_wit = IncrementalWitness::from_tree(ct); let mut out_bytes: Vec = Vec::new(); let result = write_incremental_witness(&inc_wit, &mut out_bytes); diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index ab4bd54..71c73e5 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -212,7 +212,7 @@ import ZcashHaskell.Types #} {# fun unsafe rust_wrapper_read_sapling_witness as rustWrapperReadSaplingWitness - { toBorshVar* `BS.ByteString'& + { toBorshVar* `SaplingFrontier'& , getVarBuffer `Buffer HexString'& } -> `()' diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 4b7c286..e15bf4f 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -210,15 +210,13 @@ updateSaplingCommitmentTree tree cmu = rustWrapperReadSaplingCommitmentTree tree (hexBytes cmu) -- | Get the Sapling incremental witness from a commitment tree -getSaplingWitness :: SaplingCommitmentTree -> Maybe SaplingWitness +getSaplingWitness :: SaplingFrontier -> Maybe SaplingWitness getSaplingWitness tree = if BS.length (hexBytes wit) > 1 then Just $ SaplingWitness wit else Nothing where - wit = - withPureBorshVarBuffer $ - rustWrapperReadSaplingWitness (hexBytes $ sapTree tree) + wit = withPureBorshVarBuffer $ rustWrapperReadSaplingWitness tree -- | Get the Sapling note position from a witness getSaplingNotePosition :: SaplingWitness -> Integer -- 2.34.1 From 5a9ed11c258170855fb17c9c2bdd8944b550f4b0 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 18 Oct 2024 12:25:30 -0500 Subject: [PATCH 18/23] fix(rust): SaplingFrontier marshalling --- librustzcash-wrapper/src/lib.rs | 46 ++++++++++----------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 3209737..92e9319 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -1403,39 +1403,21 @@ pub extern "C" fn rust_wrapper_read_sapling_commitment_tree( out: *mut u8, out_len: &mut usize ){ - let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); - let tree_reader = Cursor::new(tree_in); - let mut ct = read_commitment_tree::>, SAPLING_DEPTH>(tree_reader); - match ct { - Ok(mut comm_tree) => { - let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); - let sap_note_comm = SaplingNoteCommitment::from_bytes(&to_array(node_in)); - if sap_note_comm.is_some().into() { - let n = Node::from_cmu(&sap_note_comm.unwrap()); - comm_tree.append(n); - let mut out_bytes: Vec = Vec::new(); - let result = write_commitment_tree(&comm_tree, &mut out_bytes ); - match result { - Ok(()) => { - let h = Hhex { bytes: out_bytes}; - marshall_to_haskell_var(&h, out, out_len, RW); - }, - Err(_e) => { - let h0 = Hhex { bytes: vec![0]}; - marshall_to_haskell_var(&h0, out, out_len, RW); - } - } - } else { - let h0 = Hhex { bytes: vec![0]}; - marshall_to_haskell_var(&h0, out, out_len, RW); - } - }, - Err(_e) => { - let h0 = Hhex { bytes: vec![0]}; - marshall_to_haskell_var(&h0, out, out_len, RW); - } + let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); + let leaf = Node::from_bytes(to_array(tree_in.leaf.bytes)).unwrap(); + let mut comm_tree = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| Node::from_bytes(to_array(x.clone())).unwrap() ).collect()).unwrap(); + let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); + let sap_note_comm = SaplingNoteCommitment::from_bytes(&to_array(node_in)); + if sap_note_comm.is_some().into() { + let n = Node::from_cmu(&sap_note_comm.unwrap()); + comm_tree.append(n); + let (pos, leaf, omm) = comm_tree.into_parts(); + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; + marshall_to_haskell_var(&f, out, out_len, RW); + } else { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + marshall_to_haskell_var(&f0, out, out_len, RW); } - } #[no_mangle] -- 2.34.1 From 4608577c417c1fbfde54bcd502673e413e1f95a5 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 18 Oct 2024 14:39:05 -0500 Subject: [PATCH 19/23] docs: version bump --- CHANGELOG.md | 6 ++++++ zcash-haskell.cabal | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1feacf7..3bdb344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.7.2.0] + +### Changed + +- Modified Sapling commitment trees to use Frontier + ## [0.7.1.1] ### Added diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index babe1f3..edad0f8 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.1.1 +version: 0.7.2.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From 6d4b6840d30fe1631902acd0388bef0040fee9e8 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 30 Oct 2024 16:47:11 +0000 Subject: [PATCH 20/23] Optimize transaction creation (#98) Reviewed-on: https://git.vergara.tech///Vergara_Tech/zcash-haskell/pulls/98 Co-authored-by: Rene Vergara Co-committed-by: Rene Vergara --- CHANGELOG.md | 12 ++ librustzcash-wrapper/src/lib.rs | 286 +++++++++++++++++++++++++------- src/C/Zcash.chs | 30 ++++ src/ZcashHaskell/Orchard.hs | 36 +++- src/ZcashHaskell/Types.hs | 4 +- src/ZcashHaskell/Utils.hs | 70 ++++---- test/Spec.hs | 181 ++++++++++---------- zcash-haskell.cabal | 2 +- 8 files changed, 437 insertions(+), 184 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bdb344..3f3a7e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ 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.7.3.0] + +### Added + +- Function to create an Orchard hash from a note commitment +- Function to hash Orchard commitments + +### Changed + +- Modified frontiers to use `HexString` for ommers +- Optimized `createTransaction` + ## [0.7.2.0] ### Changed diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 92e9319..a469cb8 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -20,9 +20,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use haskell_ffi::{ error::Result, - from_haskell::{marshall_from_haskell_var, marshall_from_haskell_fixed}, - to_haskell::{marshall_to_haskell_var, marshall_to_haskell_fixed}, - FromHaskell, HaskellSize, ToHaskell + from_haskell::marshall_from_haskell_var, + to_haskell::marshall_to_haskell_var, + FromHaskell, ToHaskell }; use secp256k1::SecretKey; @@ -30,6 +30,8 @@ use secp256k1::SecretKey; use jubjub::Fr; use incrementalmerkletree::{ + Hashable, + Level, Position, frontier::{ CommitmentTree, @@ -40,8 +42,7 @@ use incrementalmerkletree::{ }; use zip32::{ - Scope as SaplingScope, - ChildIndex + Scope as SaplingScope }; @@ -92,7 +93,8 @@ use zcash_primitives::{ write_commitment_tree, read_incremental_witness, write_incremental_witness, - read_frontier_v1 + read_frontier_v1, + read_nonempty_frontier_v1 }, legacy::{ Script, @@ -697,7 +699,7 @@ impl ToHaskell for Hsvk { pub struct Hfrontier { position: u64, leaf: Hhex, - ommers: Vec> + ommers: Vec } impl ToHaskell for Hfrontier { @@ -1384,11 +1386,11 @@ pub extern "C" fn rust_wrapper_read_sapling_frontier( match frontier.value() { Some(f1) => { let (pos, leaf, omm) = f1.clone().into_parts(); - let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| Hhex { bytes: x.to_bytes().to_vec()}).collect()}; marshall_to_haskell_var(&f, out, out_len, RW); }, None => { - let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; marshall_to_haskell_var(&f0, out, out_len, RW); } } @@ -1405,17 +1407,17 @@ pub extern "C" fn rust_wrapper_read_sapling_commitment_tree( ){ let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); let leaf = Node::from_bytes(to_array(tree_in.leaf.bytes)).unwrap(); - let mut comm_tree = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| Node::from_bytes(to_array(x.clone())).unwrap() ).collect()).unwrap(); + let mut comm_tree = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| Node::from_bytes(to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); let sap_note_comm = SaplingNoteCommitment::from_bytes(&to_array(node_in)); if sap_note_comm.is_some().into() { let n = Node::from_cmu(&sap_note_comm.unwrap()); comm_tree.append(n); let (pos, leaf, omm) = comm_tree.into_parts(); - let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| Hhex { bytes: x.to_bytes().to_vec()}).collect()}; marshall_to_haskell_var(&f, out, out_len, RW); } else { - let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; marshall_to_haskell_var(&f0, out, out_len, RW); } } @@ -1429,7 +1431,7 @@ pub extern "C" fn rust_wrapper_read_sapling_witness( ){ let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); let leaf = Node::from_bytes(to_array(tree_in.leaf.bytes)).unwrap(); - let frontier: Frontier = Frontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| Node::from_bytes(to_array(x.clone())).unwrap() ).collect()).unwrap(); + let frontier: Frontier = Frontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| Node::from_bytes(to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); let ct: CommitmentTree = CommitmentTree::from_frontier(&frontier); let inc_wit = IncrementalWitness::from_tree(ct); let mut out_bytes: Vec = Vec::new(); @@ -1501,23 +1503,120 @@ pub extern "C" fn rust_wrapper_read_orchard_frontier( ){ let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); let tree_reader = Cursor::new(tree_in); - let comm_tree: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); - //let comm_tree: Frontier = read_frontier_v1(tree_reader).unwrap(); - let frontier: Frontier = comm_tree.to_frontier(); - match frontier.value() { - Some(f1) => { - let (pos, leaf, omm) = f1.clone().into_parts(); - let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; - marshall_to_haskell_var(&f, out, out_len, RW); + let comm_tree = read_commitment_tree(tree_reader); + //let comm_tree = read_frontier_v1(tree_reader); + //let frontier: Frontier = comm_tree.to_frontier(); + match comm_tree { + Ok::, _>(f1) => { + let frontier = f1.to_frontier(); + match frontier.value() { + Some(f2) => { + let (pos, leaf, omm) = f2.clone().into_parts(); + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| Hhex { bytes: x.to_bytes().to_vec()}).collect()}; + marshall_to_haskell_var(&f, out, out_len, RW); + }, + None => { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; + marshall_to_haskell_var(&f0, out, out_len, RW); + } + } }, - None => { - let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + Err(_e) => { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; marshall_to_haskell_var(&f0, out, out_len, RW); } } } +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_tree_anchor( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); + let leaf = MerkleHashOrchard::from_bytes(&to_array(tree_in.leaf.bytes)).unwrap(); + let comm_tree: NonEmptyFrontier = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); + let root = comm_tree.root(None); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); +} +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_witness_anchor( + wit: *const u8, + wit_len: usize, + out: *mut u8, + out_len: &mut usize + ) { + let wit_in: Vec = marshall_from_haskell_var(wit, wit_len, RW); + let wit_reader = Cursor::new(wit_in); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let root = iw.root(); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_node( + cmx: *const u8, + cmx_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let node_in: Vec = marshall_from_haskell_var(cmx, cmx_len, RW); + let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(node_in)); + if orchard_note_comm.is_some().into() { + let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); + let h = Hhex { bytes: n.to_bytes().to_vec()}; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_combine_orchard_nodes( + level: u8, + left: *const u8, + left_len: usize, + right: *const u8, + right_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let left_in: Vec = marshall_from_haskell_var(left, left_len, RW); + let right_in: Vec = marshall_from_haskell_var(right, right_len, RW); + if left_in.len() == 1 { + let n = MerkleHashOrchard::combine(Level::new(level), &MerkleHashOrchard::empty_leaf(), &MerkleHashOrchard::empty_leaf()); + let h = Hhex { bytes: n.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let left_node = MerkleHashOrchard::from_bytes(&to_array(left_in)); + if left_node.is_some().into() { + if right_in.len() > 1 { + let right_node = MerkleHashOrchard::from_bytes(&to_array(right_in)); + if right_node.is_some().into() { + let n = MerkleHashOrchard::combine(Level::new(level), &left_node.unwrap(), &right_node.unwrap()); + let h = Hhex { bytes: n.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } else { + let n = MerkleHashOrchard::combine(Level::new(level), &left_node.unwrap(), &MerkleHashOrchard::empty_leaf()); + let h = Hhex { bytes: n.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} #[no_mangle] @@ -1534,17 +1633,17 @@ pub extern "C" fn rust_wrapper_read_orchard_commitment_tree( //let mut comm_tree: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); //let mut comm_tree: Frontier = read_frontier_v1(tree_reader).unwrap(); let leaf = MerkleHashOrchard::from_bytes(&to_array(tree_in.leaf.bytes)).unwrap(); - let mut comm_tree: NonEmptyFrontier = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.clone())).unwrap() ).collect()).unwrap(); + let mut comm_tree: NonEmptyFrontier = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(node_in)); if orchard_note_comm.is_some().into() { let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); comm_tree.append(n); let (pos, leaf, omm) = comm_tree.into_parts(); - let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| x.to_bytes().to_vec()).collect()}; + let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| Hhex { bytes: x.to_bytes().to_vec()}).collect()}; marshall_to_haskell_var(&f, out, out_len, RW); } else { - let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![vec![0]]}; + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; marshall_to_haskell_var(&f0, out, out_len, RW); } } @@ -1558,7 +1657,7 @@ pub extern "C" fn rust_wrapper_read_orchard_witness( ){ let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); let leaf = MerkleHashOrchard::from_bytes(&to_array(tree_in.leaf.bytes)).unwrap(); - let frontier: Frontier = Frontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.clone())).unwrap() ).collect()).unwrap(); + let frontier: Frontier = Frontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); let ct: CommitmentTree = CommitmentTree::from_frontier(&frontier); let inc_wit = IncrementalWitness::from_tree(ct); let mut out_bytes: Vec = Vec::new(); @@ -1695,43 +1794,41 @@ pub extern "C" fn rust_wrapper_create_transaction( build: bool, out: *mut u8, out_len: &mut usize){ - //let sap_wit_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); - //let sap_wit_reader = Cursor::new(sap_wit_in); - //let sap_iw = read_commitment_tree::>, SAPLING_DEPTH>(sap_wit_reader); let sap_input: Vec = marshall_from_haskell_var(s_input, s_input_len, RW); let sap_anchor = if sap_input.is_empty() { - None + let sap_wit_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); + let sap_wit_reader = Cursor::new(sap_wit_in); + let sap_iw = read_commitment_tree::>, SAPLING_DEPTH>(sap_wit_reader); + match sap_iw { + Ok(s_iw) => { + Some(SaplingAnchor::from(s_iw.root())) + }, + Err(_e) => { + None + } + } } else { let si = &sap_input[0]; let swit_reader = Cursor::new(&si.iw); let iw: IncrementalWitness = read_incremental_witness(swit_reader).unwrap(); Some(SaplingAnchor::from(iw.root())) }; - //let sap_anchor = match sap_iw { - //Ok(s_iw) => { - //Some(SaplingAnchor::from(s_iw.root())) - //}, - //Err(_e) => { - //None - //} - //}; //println!("{:?}", sap_anchor); - //let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); - //let orch_wit_reader = Cursor::new(orch_wit_in); - //let orch_iw = read_commitment_tree::>, 32>(orch_wit_reader); let orch_input: Vec = marshall_from_haskell_var(o_input, o_input_len, RW); - //let orch_anchor = match orch_iw { - //Ok(o_iw) => { - //Some(OrchardAnchor::from(o_iw.root())) - //}, - //Err(_e) => { - //None - //} - //}; let orch_anchor = if orch_input.is_empty() { - None + let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); + let orch_wit_reader = Cursor::new(orch_wit_in); + let orch_iw = read_commitment_tree::>, 32>(orch_wit_reader); + match orch_iw { + Ok(o_iw) => { + Some(OrchardAnchor::from(o_iw.root())) + }, + Err(_e) => { + None + } + } } else { let oi = &orch_input[0]; let wit_reader = Cursor::new(&oi.iw); @@ -1744,6 +1841,7 @@ pub extern "C" fn rust_wrapper_create_transaction( let trans_input: Vec = marshall_from_haskell_var(t_input, t_input_len, RW); for t_in in trans_input { if t_in.sk.len() > 1 { + //println!("t inp: {:?}", t_in); let k = SecretKey::from_slice(&t_in.sk).unwrap(); if net { match main_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) { @@ -1766,6 +1864,7 @@ pub extern "C" fn rust_wrapper_create_transaction( } for s_in in sap_input { if s_in.sk.len() > 1 { + //println!("s inp: {:?}", s_in); let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk); match sp_key { Ok(sk) => { @@ -1817,6 +1916,7 @@ pub extern "C" fn rust_wrapper_create_transaction( let rho = Rho::from_bytes(&to_array(o_in.note.rho)).unwrap(); let rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap(); let val = NoteValue::from_raw(o_in.note.note); + //println!("o inp: {:?}", val); let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap(); let wit_reader = Cursor::new(o_in.iw); let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); @@ -1852,19 +1952,49 @@ pub extern "C" fn rust_wrapper_create_transaction( 1 => { let recipient = TransparentAddress::PublicKeyHash(to_array(output.to)); let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + //println!("t out: {:?} {:?}", val, output.chg); if net { - let _mb = main_builder.add_transparent_output(&recipient, val); + let mb = main_builder.add_transparent_output(&recipient, val); + match mb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![4]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } else { - let _tb = test_builder.add_transparent_output(&recipient, val); + let tb = test_builder.add_transparent_output(&recipient, val); + match tb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![4]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } }, 2 => { let recipient = TransparentAddress::ScriptHash(to_array(output.to)); let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + //println!("t out: {:?} {:?}", val, output.chg); if net { - let _mb = main_builder.add_transparent_output(&recipient, val); + let mb = main_builder.add_transparent_output(&recipient, val); + match mb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![4]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } else { - let _tb = test_builder.add_transparent_output(&recipient, val); + let tb = test_builder.add_transparent_output(&recipient, val); + match tb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![4]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } }, 3 => { @@ -1872,10 +2002,26 @@ pub extern "C" fn rust_wrapper_create_transaction( let recipient = PaymentAddress::from_bytes(&to_array(output.to)).unwrap(); let val = NonNegativeAmount::from_u64(output.amt).unwrap(); let memo = MemoBytes::from_bytes(&output.memo).unwrap(); + //println!("s out: {:?} {:?}", val, output.chg); if net { - let _mb = main_builder.add_sapling_output::(ovk, recipient, val, memo); + let mb = main_builder.add_sapling_output::(ovk, recipient, val, memo); + match mb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![5]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } else { - let _tb = test_builder.add_sapling_output::(ovk, recipient, val, memo); + let tb = test_builder.add_sapling_output::(ovk, recipient, val, memo); + match tb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![5]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } + //println!("add sap: {:?}", tb); } }, 4 => { @@ -1887,11 +2033,26 @@ pub extern "C" fn rust_wrapper_create_transaction( }; let recipient = OrchardAddress::from_raw_address_bytes(&to_array(output.to)).unwrap(); let val = output.amt; + //println!("o out: {:?} {:?}", val, output.chg); let memo = MemoBytes::from_bytes(&output.memo).unwrap(); if net { - let _mb = main_builder.add_orchard_output::(ovk, recipient, val, memo); + let mb = main_builder.add_orchard_output::(ovk, recipient, val, memo); + match mb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![6]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } else { - let _tb = test_builder.add_orchard_output::(ovk, recipient, val, memo); + let tb = test_builder.add_orchard_output::(ovk, recipient, val, memo); + match tb { + Ok(()) => { continue; }, + Err(_e) => { + let x = Hhex {bytes: vec![6]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } }, _ => { @@ -1900,6 +2061,12 @@ pub extern "C" fn rust_wrapper_create_transaction( } } if build { + //let fee_result = if net { + //main_builder.get_fee(&FeeRule::standard()) + //} else { + //test_builder.get_fee(&FeeRule::standard()) + //}; + //println!("fee: {:?}", fee_result); let (spend_params_in, output_params_in) = load_sapling_parameters(); //let spend_params_in: Vec = marshall_from_haskell_var(sapspend, sapspend_len, RW); let spend_params_reader = Cursor::new(spend_params_in); @@ -1926,6 +2093,7 @@ pub extern "C" fn rust_wrapper_create_transaction( marshall_to_haskell_var(&x, out, out_len, RW); }, Error::ChangeRequired(y1) => { + //println!("change req: {:?}", y1); let x = Hhex {bytes: vec![1]}; marshall_to_haskell_var(&x, out, out_len, RW); }, diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 71c73e5..da2f86b 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -246,6 +246,36 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_orchard_node as rustWrapperReadOrchardNode + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_combine_orchard_nodes as rustWrapperCombineOrchardNodes + { `Int8' + , toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_orchard_tree_anchor as rustWrapperReadOrchardTreeAnchor + { toBorshVar* `OrchardFrontier'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_orchard_witness_anchor as rustWrapperReadOrchardWitnessAnchor + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_read_orchard_commitment_tree as rustWrapperReadOrchardCommitmentTree { toBorshVar* `OrchardFrontier'& , toBorshVar* `BS.ByteString'& diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 1bb7167..79043ea 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -18,15 +18,19 @@ module ZcashHaskell.Orchard where import C.Zcash - ( rustWrapperGenOrchardReceiver + ( rustWrapperCombineOrchardNodes + , rustWrapperGenOrchardReceiver , rustWrapperGenOrchardSpendKey , rustWrapperOrchardCheck , rustWrapperOrchardNoteDecode , rustWrapperOrchardNoteDecodeSK , rustWrapperReadOrchardCommitmentTree , rustWrapperReadOrchardFrontier + , rustWrapperReadOrchardNode , rustWrapperReadOrchardPosition + , rustWrapperReadOrchardTreeAnchor , rustWrapperReadOrchardWitness + , rustWrapperReadOrchardWitnessAnchor , rustWrapperUADecode , rustWrapperUfvkDecode , rustWrapperUpdateOrchardWitness @@ -205,6 +209,15 @@ getOrchardFrontier tree = withPureBorshVarBuffer $ rustWrapperReadOrchardFrontier $ toBytes $ orchTree tree +getOrchardTreeAnchor :: OrchardFrontier -> HexString +getOrchardTreeAnchor tree = + withPureBorshVarBuffer $ rustWrapperReadOrchardTreeAnchor tree + +getOrchardWitnessAnchor :: OrchardWitness -> HexString +getOrchardWitnessAnchor wit = + withPureBorshVarBuffer $ + rustWrapperReadOrchardWitnessAnchor $ toBytes $ orchWit wit + -- | Update a Orchard commitment tree updateOrchardCommitmentTree :: OrchardFrontier -- ^ the base tree @@ -244,6 +257,27 @@ updateOrchardWitness wit cmus = (map toBytes cmus) else wit +getOrchardNodeValue :: BS.ByteString -> Maybe HexString +getOrchardNodeValue cmx = + if BS.length (hexBytes n) > 1 + then Just n + else Nothing + where + n = withPureBorshVarBuffer $ rustWrapperReadOrchardNode cmx + +combineOrchardNodes :: Integer -> HexString -> HexString -> Maybe HexString +combineOrchardNodes level n1 n2 = + if BS.length (hexBytes r) > 1 + then Just r + else Nothing + where + r = + withPureBorshVarBuffer $ + rustWrapperCombineOrchardNodes + (fromIntegral level) + (toBytes n1) + (toBytes n2) + -- | Parse a potential Zcash address parseAddress :: BS.ByteString -> Maybe ValidAddress parseAddress t = diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 8105d30..324216f 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -614,7 +614,7 @@ newtype SaplingCommitmentTree = SaplingCommitmentTree data SaplingFrontier = SaplingFrontier { sf_pos :: !Int64 , sf_leaf :: !HexString - , sf_ommers :: ![BS.ByteString] + , sf_ommers :: ![HexString] } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) @@ -730,7 +730,7 @@ newtype OrchardCommitmentTree = OrchardCommitmentTree data OrchardFrontier = OrchardFrontier { of_pos :: !Int64 , of_leaf :: !HexString - , of_ommers :: ![BS.ByteString] + , of_ommers :: ![HexString] } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index e7e1aae..ee0a0d9 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -132,40 +132,36 @@ createTransaction :: -> ZcashNet -- ^ the network to be used -> Int -- ^ target block height -> Bool -- ^ True to build, False to estimate fee - -> Either TxError HexString -createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing znet bh build = - processResult $! txResult - where - processResult :: HexString -> Either TxError HexString - processResult input = - if BS.length (hexBytes input) > 1 - then Right input - else case head (BS.unpack $ hexBytes input) of - 0 -> Left InsufficientFunds - 1 -> Left ChangeRequired - 2 -> Left Fee - 3 -> Left Balance - 4 -> Left TransparentBuild - 5 -> Left SaplingBuild - 6 -> Left OrchardBuild - 7 -> Left OrchardSpend - 8 -> Left OrchardRecipient - 9 -> Left SaplingBuilderNotAvailable - 10 -> Left OrchardBuilderNotAvailable - _ -> Left ZHError - txResult = - withPureBorshVarBuffer $ - rustWrapperCreateTx - (case sapAnchor of - Nothing -> "0" - Just sA -> toBytes $ sapTree sA) - (case orchAnchor of - Nothing -> "0" - Just oA -> toBytes $ orchTree oA) - tSpend - sSpend - oSpend - outgoing - (znet == MainNet) - (fromIntegral bh) - build + -> IO (Either TxError HexString) +createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing znet bh build = do + txResult <- + withBorshBufferOfInitSize 51200 $ + rustWrapperCreateTx + (case sapAnchor of + Nothing -> "0" + Just sA -> toBytes $ sapTree sA) + (case orchAnchor of + Nothing -> "0" + Just oA -> toBytes $ orchTree oA) + tSpend + sSpend + oSpend + outgoing + (znet == MainNet) + (fromIntegral bh) + build + if BS.length (hexBytes txResult) > 1 + then pure $ Right txResult + else case head (BS.unpack $ hexBytes txResult) of + 0 -> pure $ Left InsufficientFunds + 1 -> pure $ Left ChangeRequired + 2 -> pure $ Left Fee + 3 -> pure $ Left Balance + 4 -> pure $ Left TransparentBuild + 5 -> pure $ Left SaplingBuild + 6 -> pure $ Left OrchardBuild + 7 -> pure $ Left OrchardSpend + 8 -> pure $ Left OrchardRecipient + 9 -> pure $ Left SaplingBuilderNotAvailable + 10 -> pure $ Left OrchardBuilderNotAvailable + _ -> pure $ Left ZHError diff --git a/test/Spec.hs b/test/Spec.hs index ac744cb..0547e16 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -55,6 +55,7 @@ import ZcashHaskell.Sapling , genSaplingInternalAddress , genSaplingPaymentAddress , genSaplingSpendingKey + , getSaplingFrontier , getSaplingNotePosition , getSaplingWitness , getShieldedOutputs @@ -73,6 +74,7 @@ import ZcashHaskell.Types , OrchardAction(..) , OrchardBundle(..) , OrchardCommitmentTree(..) + , OrchardFrontier(..) , OrchardSpendingKey(..) , OrchardWitness(..) , Phrase(..) @@ -88,6 +90,7 @@ import ZcashHaskell.Types , SaplingAddress(..) , SaplingBundle(..) , SaplingCommitmentTree(..) + , SaplingFrontier(..) , SaplingReceiver(..) , SaplingSpendingKey(..) , SaplingWitness(..) @@ -895,33 +898,35 @@ main = do Just t' -> do let tb = zt_tBundle t' show tb `shouldNotBe` "" - describe "Sapling commitment trees" $ do - let tree = - SaplingCommitmentTree $ - hexString - "01916df07670600aefa3b412a120d6b8d9a3d2ff9466a7ec770cd52d34ddb42313001000013c60b031a5e44650059fcc7101a3f551b807ab8b3a116a5a9c7fa0f3babbe735017c0d36686294ff19d59e58b6a2ac6a7ad607a804bc202c84012d8e94f233970c0128dbde5180af5304d8577376d78297130b615a327974c10881f6d876869aea05011b80b4ca60f74dfe33c78b062df73c84b8b44dab4604db16f5b61eea40134373010c96e4cc8a6a80fba0d41e4eb3070d80769104dc33fb61133b1304c15bf9e23e000107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" - let cmu1 = - hexString - "45e47c5df6f5c5e48aa3526e977b2d1b57eda57214e36f06128008cb17b0125f" - let cmu2 = - hexString - "426ef44b3b22e0eeda7e4d2b62bac63966572b224e50f97ee56c9490cde4910d" - let tree2 = - hexString - "01a47029e9b43722c57143a5d07681bff3e2315c9a28ad49d69e7c1f2f6e81ac160010000000000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" - it "Commitment tree is updated correctly" $ do - let t1 = updateSaplingCommitmentTree tree cmu1 - t1 `shouldNotBe` Nothing - it "Incremental witness is generated" $ do - let t1 = updateSaplingCommitmentTree tree cmu1 - case t1 of - Nothing -> assertFailure "Failed to append node to tree" - Just t -> getSaplingWitness t `shouldNotBe` Nothing - it "Position of note is obtained" $ do - let p = - getSaplingNotePosition <$> - (getSaplingWitness =<< updateSaplingCommitmentTree tree cmu1) - p `shouldBe` Just 129405 + {- + -describe "Sapling commitment trees" $ do + - let tree = + - SaplingCommitmentTree $ + - hexString + - "01916df07670600aefa3b412a120d6b8d9a3d2ff9466a7ec770cd52d34ddb42313001000013c60b031a5e44650059fcc7101a3f551b807ab8b3a116a5a9c7fa0f3babbe735017c0d36686294ff19d59e58b6a2ac6a7ad607a804bc202c84012d8e94f233970c0128dbde5180af5304d8577376d78297130b615a327974c10881f6d876869aea05011b80b4ca60f74dfe33c78b062df73c84b8b44dab4604db16f5b61eea40134373010c96e4cc8a6a80fba0d41e4eb3070d80769104dc33fb61133b1304c15bf9e23e000107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" + - let cmu1 = + - hexString + - "45e47c5df6f5c5e48aa3526e977b2d1b57eda57214e36f06128008cb17b0125f" + - let cmu2 = + - hexString + - "426ef44b3b22e0eeda7e4d2b62bac63966572b224e50f97ee56c9490cde4910d" + - let tree2 = + - hexString + - "01a47029e9b43722c57143a5d07681bff3e2315c9a28ad49d69e7c1f2f6e81ac160010000000000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" + - it "Commitment tree is updated correctly" $ do + - let t1 = updateSaplingCommitmentTree tree cmu1 + - t1 `shouldNotBe` Nothing + - it "Incremental witness is generated" $ do + - let t1 = updateSaplingCommitmentTree tree cmu1 + - case t1 of + - Nothing -> assertFailure "Failed to append node to tree" + - Just t -> getSaplingWitness t `shouldNotBe` Nothing + - it "Position of note is obtained" $ do + - let p = + - getSaplingNotePosition <$> + - (getSaplingWitness =<< updateSaplingCommitmentTree tree cmu1) + - p `shouldBe` Just 129405 + -} {- describe "Orchard commitment trees" $ do let tree = OrchardCommitmentTree $ @@ -1061,18 +1066,22 @@ main = do (hexString "97e5f003d16720844ba1bd157688a7697133f4bb4a33a7c91974937a1351d7af56d16d4a10bd196ddda700fcd8be517f8f9e39a17ba0eea235d98450a626be3a998ac31f35e8e082106a31fe94da11d02b73748db4aa519df6bbf25c1d62a2cf0b192c6a486bca2632fee9e4124ce2dba6f3366a14850f6a3b784d863119f52458ed774f8d63105b4f6a3d2e09cc74e3a02ec8386213087b4c849172ded6724a45c9c12744ec4a0f86a29b803b17187df5dd5f90e71d1f3f4578d4e1496e8892") it "Sap output 1" $ do - let pos = - getSaplingNotePosition <$> - (getSaplingWitness =<< - updateSaplingCommitmentTree - tree - (fromText - "fa430c51bb108db782764cff55de9c6b11bbecd2493d2e0fa9f646428feef858")) - case pos of - Nothing -> assertFailure "couldn't get note position" - Just p -> do - let dn = decodeSaplingOutputEsk sk so1 TestNet External p - dn `shouldBe` Nothing + case getSaplingFrontier tree of + Nothing -> assertFailure "failed to read comm tree" + Just tree' -> do + let pos = + sf_pos <$> + updateSaplingCommitmentTree + tree' + (fromText + "fa430c51bb108db782764cff55de9c6b11bbecd2493d2e0fa9f646428feef858") + case pos of + Nothing -> assertFailure "couldn't get note position" + Just p -> do + let dn = + decodeSaplingOutputEsk sk so1 TestNet External $ + fromIntegral p + dn `shouldBe` Nothing it "Sap output 2" $ do case readZebraTransaction txHex2 of Nothing -> assertFailure "Failed to read Tx" @@ -1082,24 +1091,27 @@ main = do Nothing -> assertFailure "Failed to get sapling bundle" Just sB -> do let sOuts = sbOutputs sB - let pos = - getSaplingNotePosition <$> - (getSaplingWitness =<< - updateSaplingCommitmentTree - tree - (fromText - "d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843")) - case pos of - Nothing -> assertFailure "couldn't get note position" - Just p -> do - let dn = - decodeSaplingOutputEsk - sk - (head . tail $ sOuts) - TestNet - External - p - dn `shouldBe` Nothing + case getSaplingFrontier tree of + Nothing -> assertFailure "Failed to read tree" + Just tree' -> do + let pos = + getSaplingNotePosition <$> + (getSaplingWitness =<< + updateSaplingCommitmentTree + tree' + (fromText + "d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843")) + case pos of + Nothing -> assertFailure "couldn't get note position" + Just p -> do + let dn = + decodeSaplingOutputEsk + sk + (head . tail $ sOuts) + TestNet + External + p + dn `shouldBe` Nothing it "Decode Sapling Output from Zingo" $ do case readZebraTransaction txHex of Nothing -> assertFailure "Failed to read Tx" @@ -1112,24 +1124,26 @@ main = do Nothing -> assertFailure "Failed to get sapling bundle" Just sB -> do let sOuts = sbOutputs sB - let pos = - getSaplingNotePosition <$> - (getSaplingWitness =<< - updateSaplingCommitmentTree - tree - (fromText - "d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843")) - case pos of - Nothing -> assertFailure "couldn't get note position" - Just p -> do - let dn = - decodeSaplingOutputEsk - sK' - (head . tail $ sOuts) - MainNet - External - p - dn `shouldNotBe` Nothing + case getSaplingFrontier tree of + Nothing -> assertFailure "failed to read comm tree" + Just tree' -> do + let pos = + sf_pos <$> + updateSaplingCommitmentTree + tree' + (fromText + "d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843") + case pos of + Nothing -> assertFailure "couldn't get note position" + Just p -> do + let dn = + decodeSaplingOutputEsk + sK' + (head . tail $ sOuts) + MainNet + External + (fromIntegral p) + dn `shouldNotBe` Nothing describe "Generate an ExchangeAddress (MainNet) from transparent address" $ do let ta = decodeTransparentAddress "t1dMjvesbzdG41xgKaGU3HgwYJwSgbCK54e" it "Try to generate valid ExchangeAddress from Transparent Address" $ do @@ -1176,15 +1190,14 @@ main = do Just t1 -> case updateOrchardCommitmentTree t1 cmx1 of Nothing -> assertFailure "Failed to update frontier with cmx" - Just t2 -> - case updateOrchardCommitmentTree t2 cmx2 of - Nothing -> assertFailure "Failed to update frontier with cmx" - Just t3 -> - case updateOrchardCommitmentTree t3 cmx3 of - Nothing -> - assertFailure "Failed to update frontier with cmx" - Just t4 -> - updateOrchardCommitmentTree t4 cmx4 `shouldBe` finalTree + Just t2 -> do + case getOrchardWitness t2 of + Nothing -> assertFailure "Failed to get witness" + Just wit -> do + let uWit = updateOrchardWitness wit [cmx2, cmx3, cmx4] + Just (getOrchardWitnessAnchor uWit) `shouldBe` + getOrchardTreeAnchor <$> + finalTree describe "Witness updates" $ do it "Sapling" $ do let wit = diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index edad0f8..36ce2ae 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.2.0 +version: 0.7.3.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From 5ce149c54f8e7e53e455536f30dd27a35d05c036 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 5 Nov 2024 18:28:45 +0000 Subject: [PATCH 21/23] Functions to create and manage Orchard commitment trees (#99) This PR contains the functions to create, update and validate Orchard commitment trees. Reviewed-on: https://git.vergara.tech///Vergara_Tech/zcash-haskell/pulls/99 Co-authored-by: Rene Vergara Co-committed-by: Rene Vergara --- librustzcash-wrapper/src/lib.rs | 188 ++++++++++++++++++++++++++++++-- src/C/Zcash.chs | 33 +++++- src/ZcashHaskell/Orchard.hs | 40 ++++++- src/ZcashHaskell/Types.hs | 23 ++++ 4 files changed, 273 insertions(+), 11 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index a469cb8..7657829 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -36,7 +36,8 @@ use incrementalmerkletree::{ frontier::{ CommitmentTree, Frontier, - NonEmptyFrontier + NonEmptyFrontier, + PathFiller }, witness::IncrementalWitness }; @@ -271,6 +272,12 @@ impl ToHaskell for Hhex { } } +impl FromHaskell for Hhex { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Hhex::deserialize(buf)?; + Ok(x) + } +} #[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Haction { @@ -716,6 +723,47 @@ impl FromHaskell for Hfrontier { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Htree { + left: Hhex, + right: Hhex, + parents: Vec +} + +impl ToHaskell for Htree { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl FromHaskell for Htree { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Htree::deserialize(buf)?; + Ok(x) + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Hpath { + position: u32, + path: Vec +} + +impl ToHaskell for Hpath { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl FromHaskell for Hpath { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Hpath::deserialize(buf)?; + Ok(x) + } +} + 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())) } @@ -1513,7 +1561,13 @@ pub extern "C" fn rust_wrapper_read_orchard_frontier( Some(f2) => { let (pos, leaf, omm) = f2.clone().into_parts(); let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| Hhex { bytes: x.to_bytes().to_vec()}).collect()}; - marshall_to_haskell_var(&f, out, out_len, RW); + let comm_tree2: Frontier = Frontier::from_parts(Position::from(f.position), MerkleHashOrchard::from_bytes(&to_array(f.leaf.bytes.clone())).unwrap(), f.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); + if f1.root() == comm_tree2.root() { + marshall_to_haskell_var(&f, out, out_len, RW); + } else { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; + marshall_to_haskell_var(&f0, out, out_len, RW); + } }, None => { let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; @@ -1535,12 +1589,66 @@ pub extern "C" fn rust_wrapper_read_orchard_tree_anchor( out: *mut u8, out_len: &mut usize ){ - let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); - let leaf = MerkleHashOrchard::from_bytes(&to_array(tree_in.leaf.bytes)).unwrap(); - let comm_tree: NonEmptyFrontier = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); - let root = comm_tree.root(None); - let h = Hhex { bytes: root.to_bytes().to_vec() }; - marshall_to_haskell_var(&h, out, out_len, RW); + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree = read_commitment_tree(tree_reader); + match comm_tree { + Ok::, _>(c1) => { + let root = c1.root(); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_) => { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_commitment_tree_parts( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree = read_commitment_tree(tree_reader); + match comm_tree { + Ok::, _>(c1) => { + let left = match c1.left() { + Some(x) => { + Hhex { bytes: x.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }; + let right = match c1.right() { + Some(x) => { + Hhex { bytes: x.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }; + let parents = c1.parents().iter().map(|x| match x { + Some(y) => { + Hhex { bytes: y.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }).collect(); + let ht = Htree { left, right, parents}; + marshall_to_haskell_var(&ht, out, out_len, RW); + }, + Err(_e) => { + let ht0 = Htree { left: Hhex { bytes: vec![0] } , right: Hhex { bytes: vec![0] }, parents: vec![Hhex { bytes: vec![0] }]}; + marshall_to_haskell_var(&ht0, out, out_len, RW); + } + } } #[no_mangle] @@ -1577,6 +1685,29 @@ pub extern "C" fn rust_wrapper_read_orchard_node( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_path_anchor( + path: *const u8, + path_len: usize, + cmx: *const u8, + cmx_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let path_in: Hpath = marshall_from_haskell_var(path, path_len, RW); + let cmx_in: Vec = marshall_from_haskell_var(cmx, cmx_len, RW); + let mk_path = orchard::tree::MerklePath::from_parts(path_in.position, to_array(path_in.path.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap()).collect())); + let nc = ExtractedNoteCommitment::from_bytes(&to_array(cmx_in)); + if nc.is_some().into() { + let anchor = mk_path.root(nc.unwrap()); + let h = Hhex { bytes: anchor.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_combine_orchard_nodes( level: u8, @@ -1618,6 +1749,47 @@ pub extern "C" fn rust_wrapper_combine_orchard_nodes( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_get_orchard_root( + level: u8, + out: *mut u8, + out_len: &mut usize + ){ + let tree: CommitmentTree = CommitmentTree::empty(); + let root = tree.root(); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_orchard_add_node( + level: u8, + node: *const u8, + node_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let mut tree: CommitmentTree = CommitmentTree::empty(); + let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); + let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(node_in)); + if orchard_note_comm.is_some().into() { + let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); + match tree.append(n) { + Ok(()) => { + let root = tree.root_at_depth(level, PathFiller::empty()); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_) => { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } +} #[no_mangle] pub extern "C" fn rust_wrapper_read_orchard_commitment_tree( diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index da2f86b..a896a10 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -263,7 +263,7 @@ import ZcashHaskell.Types #} {# fun unsafe rust_wrapper_read_orchard_tree_anchor as rustWrapperReadOrchardTreeAnchor - { toBorshVar* `OrchardFrontier'& + { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer HexString'& } -> `()' @@ -276,6 +276,21 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_orchard_path_anchor as rustWrapperReadOrchardPathAnchor + { toBorshVar* `MerklePath'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_get_orchard_root as rustWrapperGetOrchardRootTest + { `Int8' + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_read_orchard_commitment_tree as rustWrapperReadOrchardCommitmentTree { toBorshVar* `OrchardFrontier'& , toBorshVar* `BS.ByteString'& @@ -284,6 +299,13 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_orchard_commitment_tree_parts as rustWrapperReadOrchardTreeParts + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer OrchardRawTree'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_read_orchard_frontier as rustWrapperReadOrchardFrontier { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer OrchardFrontier'& @@ -304,6 +326,15 @@ import ZcashHaskell.Types -> `Word64' #} +{# fun unsafe rust_wrapper_orchard_add_node as rustWrapperOrchardAddNodeTest + { `Int8' + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' + +#} + {# fun unsafe rust_wrapper_update_sapling_witness as rustWrapperUpdateSaplingWitness { toBorshVar* `BS.ByteString'& , toBorshVar* `[BS.ByteString]'& diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 79043ea..7991138 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -21,14 +21,18 @@ import C.Zcash ( rustWrapperCombineOrchardNodes , rustWrapperGenOrchardReceiver , rustWrapperGenOrchardSpendKey + , rustWrapperGetOrchardRootTest + , rustWrapperOrchardAddNodeTest , rustWrapperOrchardCheck , rustWrapperOrchardNoteDecode , rustWrapperOrchardNoteDecodeSK , rustWrapperReadOrchardCommitmentTree , rustWrapperReadOrchardFrontier , rustWrapperReadOrchardNode + , rustWrapperReadOrchardPathAnchor , rustWrapperReadOrchardPosition , rustWrapperReadOrchardTreeAnchor + , rustWrapperReadOrchardTreeParts , rustWrapperReadOrchardWitness , rustWrapperReadOrchardWitnessAnchor , rustWrapperUADecode @@ -209,15 +213,47 @@ getOrchardFrontier tree = withPureBorshVarBuffer $ rustWrapperReadOrchardFrontier $ toBytes $ orchTree tree -getOrchardTreeAnchor :: OrchardFrontier -> HexString +getOrchardTreeAnchor :: OrchardCommitmentTree -> HexString getOrchardTreeAnchor tree = - withPureBorshVarBuffer $ rustWrapperReadOrchardTreeAnchor tree + withPureBorshVarBuffer $ + rustWrapperReadOrchardTreeAnchor $ toBytes $ orchTree tree getOrchardWitnessAnchor :: OrchardWitness -> HexString getOrchardWitnessAnchor wit = withPureBorshVarBuffer $ rustWrapperReadOrchardWitnessAnchor $ toBytes $ orchWit wit +getOrchardRootTest :: Int -> HexString +getOrchardRootTest level = + withPureBorshVarBuffer $ rustWrapperGetOrchardRootTest $ fromIntegral level + +addOrchardNodeGetRoot :: Int -> BS.ByteString -> HexString +addOrchardNodeGetRoot l n = + withPureBorshVarBuffer $ rustWrapperOrchardAddNodeTest (fromIntegral l) n + +getOrchardTreeParts :: OrchardCommitmentTree -> Maybe OrchardTree +getOrchardTreeParts h = + if isBlank (ort_left tree) && isBlank (ort_right tree) + then Nothing + else Just $ + OrchardTree + (parseHex $ ort_left tree) + (parseHex $ ort_right tree) + (map parseHex (ort_parents tree)) + where + isBlank h = (BS.length $ hexBytes $ h) == 1 + parseHex h = + if (BS.length $ hexBytes $ h) > 1 + then Just h + else Nothing + tree = + withPureBorshVarBuffer $ + rustWrapperReadOrchardTreeParts $ toBytes $ orchTree h + +getOrchardPathAnchor :: HexString -> MerklePath -> HexString +getOrchardPathAnchor hex p = + withPureBorshVarBuffer $ rustWrapperReadOrchardPathAnchor p (hexBytes hex) + -- | Update a Orchard commitment tree updateOrchardCommitmentTree :: OrchardFrontier -- ^ the base tree diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 324216f..8b8a90b 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -722,11 +722,34 @@ instance FromJSON OrchardAction where a <- obj .: "spendAuthSig" pure $ OrchardAction n r c ephKey encText outText cval a +data MerklePath = MerklePath + { mp_position :: !Int32 + , mp_path :: ![HexString] + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct MerklePath + -- | Type for a Orchard note commitment tree newtype OrchardCommitmentTree = OrchardCommitmentTree { orchTree :: HexString } deriving (Eq, Prelude.Show, Read) +data OrchardRawTree = OrchardRawTree + { ort_left :: !HexString + , ort_right :: !HexString + , ort_parents :: ![HexString] + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardRawTree + +data OrchardTree = OrchardTree + { ot_left :: !(Maybe HexString) + , ot_right :: !(Maybe HexString) + , ot_parents :: ![Maybe HexString] + } deriving (Eq, Prelude.Show, Read) + data OrchardFrontier = OrchardFrontier { of_pos :: !Int64 , of_leaf :: !HexString -- 2.34.1 From 62cda9cc15621dead6fbfd7a4944840408d69da4 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 5 Nov 2024 12:42:03 -0600 Subject: [PATCH 22/23] docs: version bump --- CHANGELOG.md | 6 ++++++ zcash-haskell.cabal | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3a7e4..3cbfed3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.7.4.0] + +### Added + +- `MerklePath` + ## [0.7.3.0] ### Added diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 36ce2ae..3216af8 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.3.0 +version: 0.7.4.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1 From dea960c2acf7479eeb42845c07b482449d538aae Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 15 Nov 2024 18:48:36 +0000 Subject: [PATCH 23/23] Functions to handle commitment tree nodes for Sapling and Orchard (#100) This PR includes the functions for hashing commitment nodes for both Sapling and Orchard. It also includes functions to validate tree roots and Merkle paths. Reviewed-on: https://git.vergara.tech///Vergara_Tech/zcash-haskell/pulls/100 Co-authored-by: Rene Vergara Co-committed-by: Rene Vergara --- CHANGELOG.md | 17 ++ librustzcash-wrapper/Cargo.lock | 61 +++---- librustzcash-wrapper/Cargo.toml | 12 +- librustzcash-wrapper/src/lib.rs | 273 ++++++++++++++++++++++++++------ src/C/Zcash.chs | 45 ++++++ src/ZcashHaskell/Sapling.hs | 59 ++++++- src/ZcashHaskell/Types.hs | 19 ++- src/ZcashHaskell/Utils.hs | 12 +- zcash-haskell.cabal | 2 +- 9 files changed, 403 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cbfed3..ed642a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ 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.7.5.0] + +### Added + +- Sapling commitment node functions +- Sapling Merkle path test + +### Changed + +- Upgraded Rust dependencies to latest versions: + - `zcash_primitives` 0.19.0 + - `zcash_client_backend` 0.14.0 + - `orchard` 0.10.0 + - `sapling-crypto` 0.3.0 + - `incrementalmerkletree` 0.7.0 + - `zip32` 0.1.2 + ## [0.7.4.0] ### Added diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index 604eeee..0c20ffb 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -60,9 +60,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.21.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75346da3bd8e3d8891d02508245ed2df34447ca6637e343829f8d08986e9cde2" +checksum = "d45063fbc4b0a37837f6bfe0445f269d13d730ad0aa3b5a7f74aa7bf27a0f4df" dependencies = [ "either", ] @@ -987,9 +987,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc7bde644aeb980be296cd908c6650894dc8541deb56f9f5294c52ed7ca568f" +checksum = "4f18e997fa121de5c73e95cdc7e8512ae43b7de38904aeea5e5713cc48f3c0ba" dependencies = [ "aes", "bitvec", @@ -1379,9 +1379,9 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e379398fffad84e49f9a45a05635fc004f66086e65942dbf4eb95332c26d2a" +checksum = "cfff8cfce16aeb38da50b8e2ed33c9018f30552beff2210c266662a021b17f38" dependencies = [ "aes", "bellman", @@ -1488,9 +1488,9 @@ dependencies = [ [[package]] name = "shardtree" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78222845cd8bbe5eb95687407648ff17693a35de5e8abaa39a4681fb21e033f9" +checksum = "b5f2390975ebfe8838f9e861f7a588123d49a7a7a0a08568ea831d8ad53fc9b4" dependencies = [ "bitflags 2.4.2", "either", @@ -1883,9 +1883,9 @@ dependencies = [ [[package]] name = "zcash_address" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d26f21381dc220836dd8d2a9a10dbe85928a26232b011bc6a42b611789b743" +checksum = "4ff95eac82f71286a79c750e674550d64fb2b7aadaef7b89286b2917f645457d" dependencies = [ "bech32 0.9.1", "bs58 0.5.0", @@ -1896,9 +1896,9 @@ dependencies = [ [[package]] name = "zcash_client_backend" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e3a0f3e5d7f299d8b7ef3237697630989c31ab1b162824c99c1cd8bc83715e" +checksum = "cbeeede366fdb642710d3c59fc2090489affd075f66db53ed11bb7138d2d0258" dependencies = [ "base64", "bech32 0.9.1", @@ -1924,7 +1924,7 @@ dependencies = [ "tonic-build", "tracing", "which", - "zcash_address 0.4.0", + "zcash_address 0.6.0", "zcash_encoding", "zcash_keys", "zcash_note_encryption", @@ -1946,9 +1946,9 @@ dependencies = [ [[package]] name = "zcash_keys" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712faf4070107ab0b2828d0eda6aeaf4c3cb02564109832d95b97ad3467c95a5" +checksum = "e8162c94957f1e379b8e2fb30f97b95cfa93ac9c6bc02895946ca6392d1abb81" dependencies = [ "bech32 0.9.1", "blake2b_simd", @@ -1963,7 +1963,7 @@ dependencies = [ "secrecy", "subtle", "tracing", - "zcash_address 0.4.0", + "zcash_address 0.6.0", "zcash_encoding", "zcash_primitives", "zcash_protocol", @@ -1985,9 +1985,9 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f044bc9cf2887ec408196fbafb44749e5581f57cc18d8da7aabaeb60cc40c64" +checksum = "6ab47d526d7fd6f88b3a2854ad81b54757a80c2aeadd1d8b06f690556af9743c" dependencies = [ "aes", "bip32", @@ -2014,7 +2014,7 @@ dependencies = [ "sha2 0.10.6", "subtle", "tracing", - "zcash_address 0.4.0", + "zcash_address 0.6.0", "zcash_encoding", "zcash_note_encryption", "zcash_protocol", @@ -2024,9 +2024,9 @@ dependencies = [ [[package]] name = "zcash_protocol" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f35eac659fdbba614333d119217c5963c0d7cea43aee33176c4f2f95e5460d8d" +checksum = "6bc22b9155b2c7eb20105cd06de170d188c1bc86489b92aa3fda7b8da8d96acf" dependencies = [ "document-features", "memuse", @@ -2034,9 +2034,9 @@ dependencies = [ [[package]] name = "zcash_spec" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a3bf58b673cb3dacd8ae09ba345998923a197ab0da70d6239d8e8838949e9b" +checksum = "9cede95491c2191d3e278cab76e097a44b17fde8d6ca0d4e3a22cf4807b2d857" dependencies = [ "blake2b_simd", ] @@ -2063,24 +2063,25 @@ dependencies = [ [[package]] name = "zip32" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e" +checksum = "92022ac1e47c7b78f9cee29efac8a1a546e189506f3bb5ad46d525be7c519bf6" dependencies = [ "blake2b_simd", "memuse", "subtle", + "zcash_spec", ] [[package]] name = "zip321" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc85f862f7be64fb0d46f9eb5b82ad54e58cde314fa979d5bae591bc0143693" +checksum = "1f3e613defb0940acef1f54774b51c7f48f2fa705613dd800870dc69f35cd2ea" dependencies = [ "base64", "nom", "percent-encoding", - "zcash_address 0.4.0", + "zcash_address 0.6.0", "zcash_protocol", ] diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index 208022f..350ecb2 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -11,15 +11,15 @@ f4jumble = "0.1" zcash_address = "0.2.0" borsh = "0.10" bech32 = "0.11" -orchard = "0.9.0" +orchard = "0.10.0" zcash_note_encryption = "0.4.0" -zcash_primitives = { version = "0.16.0", features = ["transparent-inputs"]} -zcash_client_backend = "0.13.0" -sapling-crypto = "0.2" -zip32 = "0.1.0" +zcash_primitives = { version = "0.19.0", features = ["transparent-inputs"]} +zcash_client_backend = "0.14.0" +sapling-crypto = "0.3" +zip32 = "0.1.2" proc-macro2 = "1.0.66" nonempty = "0.7.0" -incrementalmerkletree = "0.6.0" +incrementalmerkletree = "0.7.0" secp256k1 = "0.27.0" jubjub = "0.10.0" rand_core = { version = "0.6.4", features = ["getrandom"]} diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 7657829..563ac5f 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -570,11 +570,11 @@ impl Hspend { } } -#[derive(Debug, BorshSerialize, BorshDeserialize)] +#[derive(BorshSerialize, BorshDeserialize)] pub struct HsaplingInput { sk: Vec, note: Hnote, - iw: Vec + iw: Hpath } impl FromHaskell for HsaplingInput { @@ -585,11 +585,11 @@ impl FromHaskell for HsaplingInput { } -#[derive(Debug, BorshSerialize, BorshDeserialize)] +#[derive(BorshSerialize, BorshDeserialize)] pub struct HorchardInput { sk: Vec, note: Hnote, - iw: Vec + iw: Hpath } impl FromHaskell for HorchardInput { @@ -1542,6 +1542,183 @@ pub extern "C" fn rust_wrapper_update_sapling_witness( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_node( + cmu: *const u8, + cmu_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let node_in: Vec = marshall_from_haskell_var(cmu, cmu_len, RW); + let sapling_note_comm = SaplingNoteCommitment::from_bytes(&to_array(node_in)); + if sapling_note_comm.is_some().into() { + let n = Node::from_cmu(&sapling_note_comm.unwrap()); + let h = Hhex { bytes: n.to_bytes().to_vec()}; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_combine_sapling_nodes( + level: u8, + left: *const u8, + left_len: usize, + right: *const u8, + right_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let left_in: Vec = marshall_from_haskell_var(left, left_len, RW); + let right_in: Vec = marshall_from_haskell_var(right, right_len, RW); + if left_in.len() == 1 { + let n = Node::combine(Level::new(level), &Node::empty_leaf(), &Node::empty_leaf()); + let h = Hhex { bytes: n.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let left_node = Node::from_bytes(to_array(left_in)); + if left_node.is_some().into() { + if right_in.len() > 1 { + let right_node = Node::from_bytes(to_array(right_in)); + if right_node.is_some().into() { + let n = Node::combine(Level::new(level), &left_node.unwrap(), &right_node.unwrap()); + let h = Hhex { bytes: n.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } else { + let n = Node::combine(Level::new(level), &left_node.unwrap(), &Node::empty_leaf()); + let h = Hhex { bytes: n.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_get_sapling_root( + level: u8, + out: *mut u8, + out_len: &mut usize + ){ + let tree: CommitmentTree = CommitmentTree::empty(); + let root = tree.root(); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_commitment_tree_parts( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree = read_commitment_tree(tree_reader); + match comm_tree { + Ok::, _>(c1) => { + let left = match c1.left() { + Some(x) => { + Hhex { bytes: x.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }; + let right = match c1.right() { + Some(x) => { + Hhex { bytes: x.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }; + let parents = c1.parents().iter().map(|x| match x { + Some(y) => { + Hhex { bytes: y.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }).collect(); + let ht = Htree { left, right, parents}; + marshall_to_haskell_var(&ht, out, out_len, RW); + }, + Err(_e) => { + let ht0 = Htree { left: Hhex { bytes: vec![0] } , right: Hhex { bytes: vec![0] }, parents: vec![Hhex { bytes: vec![0] }]}; + marshall_to_haskell_var(&ht0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_tree_anchor( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree = read_commitment_tree(tree_reader); + match comm_tree { + Ok::, _>(c1) => { + let root = c1.root(); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_) => { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_path_anchor( + path: *const u8, + path_len: usize, + cmx: *const u8, + cmx_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let path_in: Hpath = marshall_from_haskell_var(path, path_len, RW); + let cmx_in: Vec = marshall_from_haskell_var(cmx, cmx_len, RW); + let mk_path = sapling_crypto::MerklePath::from_parts(path_in.path.iter().map(|x| + if x.bytes.len() > 1 { + Node::from_bytes(to_array(x.bytes.clone())).unwrap() + } else { + Node::empty_leaf() + }).collect(), Position::from(u64::from(path_in.position))); + match mk_path { + Ok(mp1) => { + let nc = SaplingNoteCommitment::from_bytes(&to_array(cmx_in)); + if nc.is_some().into() { + let anchor = mp1.root(Node::from_cmu(&nc.unwrap())); + let h = Hhex { bytes: anchor.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + }, + Err(_e) =>{ + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_read_orchard_frontier( tree: *const u8, @@ -1696,7 +1873,13 @@ pub extern "C" fn rust_wrapper_read_orchard_path_anchor( ){ let path_in: Hpath = marshall_from_haskell_var(path, path_len, RW); let cmx_in: Vec = marshall_from_haskell_var(cmx, cmx_len, RW); - let mk_path = orchard::tree::MerklePath::from_parts(path_in.position, to_array(path_in.path.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap()).collect())); + let mk_path = orchard::tree::MerklePath::from_parts(path_in.position, to_array(path_in.path.iter().map(|x| + if x.bytes.len() > 1 { + MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() + } else { + MerkleHashOrchard::empty_leaf() + } + ).collect())); let nc = ExtractedNoteCommitment::from_bytes(&to_array(cmx_in)); if nc.is_some().into() { let anchor = mk_path.root(nc.unwrap()); @@ -1967,47 +2150,26 @@ pub extern "C" fn rust_wrapper_create_transaction( out: *mut u8, out_len: &mut usize){ let sap_input: Vec = marshall_from_haskell_var(s_input, s_input_len, RW); - let sap_anchor = - if sap_input.is_empty() { - let sap_wit_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); - let sap_wit_reader = Cursor::new(sap_wit_in); - let sap_iw = read_commitment_tree::>, SAPLING_DEPTH>(sap_wit_reader); - match sap_iw { - Ok(s_iw) => { - Some(SaplingAnchor::from(s_iw.root())) - }, - Err(_e) => { - None - } - } + let sap_anchor_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); + let sap_anchor = Node::from_bytes(to_array(sap_anchor_in)); + let sapling_anchor = + if sap_anchor.is_some().into() { + Some(SaplingAnchor::from(sap_anchor.unwrap())) } else { - let si = &sap_input[0]; - let swit_reader = Cursor::new(&si.iw); - let iw: IncrementalWitness = read_incremental_witness(swit_reader).unwrap(); - Some(SaplingAnchor::from(iw.root())) + None }; - //println!("{:?}", sap_anchor); + //println!("{:?}", sapling_anchor); let orch_input: Vec = marshall_from_haskell_var(o_input, o_input_len, RW); - let orch_anchor = - if orch_input.is_empty() { - let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); - let orch_wit_reader = Cursor::new(orch_wit_in); - let orch_iw = read_commitment_tree::>, 32>(orch_wit_reader); - match orch_iw { - Ok(o_iw) => { - Some(OrchardAnchor::from(o_iw.root())) - }, - Err(_e) => { - None - } - } + let orch_anchor_in : Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); + let orch_anchor = MerkleHashOrchard::from_bytes(&to_array(orch_anchor_in)); + let orchard_anchor = + if orch_anchor.is_some().into() { + Some(OrchardAnchor::from(orch_anchor.unwrap())) } else { - let oi = &orch_input[0]; - let wit_reader = Cursor::new(&oi.iw); - let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); - Some(OrchardAnchor::from(iw.root())) + None }; - let build_config = BuildConfig::Standard {sapling_anchor: sap_anchor, orchard_anchor: orch_anchor}; + //println!("{:?}", orchard_anchor); + let build_config = BuildConfig::Standard {sapling_anchor, orchard_anchor}; let mut main_builder = Builder::new(MainNetwork, BlockHeight::from(bl_height), build_config); let mut test_builder = Builder::new(TestNetwork, BlockHeight::from(bl_height), build_config); let trans_input: Vec = marshall_from_haskell_var(t_input, t_input_len, RW); @@ -2047,9 +2209,13 @@ pub extern "C" fn rust_wrapper_create_transaction( Rseed::AfterZip212(to_array(s_in.note.rseed.bytes)) }; let note = SaplingNote::from_parts(pay_addr, SaplingNoteValue::from_raw(s_in.note.note), rseed); - let wit_reader = Cursor::new(s_in.iw); - let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); - let merkle_path = iw.path().unwrap(); + let mk_path = sapling_crypto::MerklePath::from_parts(s_in.iw.path.iter().map(|x| + if x.bytes.len() > 1 { + Node::from_bytes(to_array(x.bytes.clone())).unwrap() + } else { + Node::empty_leaf() + }).collect(), Position::from(u64::from(s_in.iw.position))); + let merkle_path = mk_path.unwrap(); if net { let mb = main_builder.add_sapling_spend::(&sk, note, merkle_path); match mb { @@ -2090,16 +2256,22 @@ pub extern "C" fn rust_wrapper_create_transaction( let val = NoteValue::from_raw(o_in.note.note); //println!("o inp: {:?}", val); let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap(); - let wit_reader = Cursor::new(o_in.iw); - let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); - let merkle_path = OrchardMerklePath::from(iw.path().unwrap()); + let merkle_path = orchard::tree::MerklePath::from_parts(o_in.iw.position, to_array(o_in.iw.path.iter().map(|x| + if x.bytes.len() > 1 { + MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() + } else { + MerkleHashOrchard::empty_leaf() + } + ).collect())); if net { let mb = main_builder.add_orchard_spend::(&sp_key, note, merkle_path); match mb { Ok(()) => { + //println!("added orchard inp: {:?}", val); continue; }, - Err(_e) => { + Err(e) => { + //println!("failed orchard inp: {:?}", e); let x = Hhex {bytes: vec![7]}; marshall_to_haskell_var(&x, out, out_len, RW); } @@ -2108,9 +2280,11 @@ pub extern "C" fn rust_wrapper_create_transaction( let tb = test_builder.add_orchard_spend::(&sp_key, note, merkle_path); match tb { Ok(()) => { + //println!("added orchard inp: {:?}", val); continue; }, - Err(_e) => { + Err(e) => { + //println!("failed orchard inp: {:?}", e); let x = Hhex {bytes: vec![7]}; marshall_to_haskell_var(&x, out, out_len, RW); } @@ -2261,6 +2435,7 @@ pub extern "C" fn rust_wrapper_create_transaction( Err(e) => { match e { Error::InsufficientFunds(y) => { + //println!("ins funds: {:?}", y); let x = Hhex {bytes: vec![0]}; marshall_to_haskell_var(&x, out, out_len, RW); }, diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index a896a10..fde4550 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -246,6 +246,51 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_sapling_node as rustWrapperReadSaplingNode + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_combine_sapling_nodes as rustWrapperCombineSaplingNodes + { `Int8' + , toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_get_sapling_root as rustWrapperGetSaplingRootTest + { `Int8' + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_sapling_commitment_tree_parts as rustWrapperReadSaplingTreeParts + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer SaplingRawTree'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_sapling_tree_anchor as rustWrapperReadSaplingTreeAnchor + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_sapling_path_anchor as rustWrapperReadSaplingPathAnchor + { toBorshVar* `MerklePath'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_read_orchard_node as rustWrapperReadOrchardNode { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer HexString'& diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index e15bf4f..1347778 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -18,11 +18,17 @@ module ZcashHaskell.Sapling where import C.Zcash - ( rustWrapperDecodeSaplingAddress + ( rustWrapperCombineSaplingNodes + , rustWrapperDecodeSaplingAddress + , rustWrapperGetSaplingRootTest , rustWrapperIsShielded , rustWrapperReadSaplingCommitmentTree , rustWrapperReadSaplingFrontier + , rustWrapperReadSaplingNode + , rustWrapperReadSaplingPathAnchor , rustWrapperReadSaplingPosition + , rustWrapperReadSaplingTreeAnchor + , rustWrapperReadSaplingTreeParts , rustWrapperReadSaplingWitness , rustWrapperSaplingCheck , rustWrapperSaplingChgPaymentAddress @@ -38,6 +44,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 qualified Data.Text as T import Data.Word import Foreign.Rust.Marshall.Variable @@ -185,6 +192,56 @@ genSaplingInternalAddress sk = res = withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk) +getSaplingNodeValue :: BS.ByteString -> Maybe HexString +getSaplingNodeValue cmu = + if BS.length (hexBytes n) > 1 + then Just n + else Nothing + where + n = withPureBorshVarBuffer $ rustWrapperReadSaplingNode cmu + +combineSaplingNodes :: Int8 -> HexString -> HexString -> Maybe HexString +combineSaplingNodes level n1 n2 = + if BS.length (hexBytes r) > 1 + then Just r + else Nothing + where + r = + withPureBorshVarBuffer $ + rustWrapperCombineSaplingNodes level (toBytes n1) (toBytes n2) + +getSaplingRootTest :: Int8 -> HexString +getSaplingRootTest level = + withPureBorshVarBuffer $ rustWrapperGetSaplingRootTest level + +getSaplingTreeParts :: SaplingCommitmentTree -> Maybe SaplingTree +getSaplingTreeParts h = + if isBlank (srt_left tree) && isBlank (srt_right tree) + then Nothing + else Just $ + SaplingTree + (parseHex $ srt_left tree) + (parseHex $ srt_right tree) + (map parseHex (srt_parents tree)) + where + isBlank h = (BS.length $ hexBytes $ h) == 1 + parseHex h = + if (BS.length $ hexBytes $ h) > 1 + then Just h + else Nothing + tree = + withPureBorshVarBuffer $ + rustWrapperReadSaplingTreeParts $ toBytes $ sapTree h + +getSaplingTreeAnchor :: SaplingCommitmentTree -> HexString +getSaplingTreeAnchor tree = + withPureBorshVarBuffer $ + rustWrapperReadSaplingTreeAnchor $ toBytes $ sapTree tree + +getSaplingPathAnchor :: HexString -> MerklePath -> HexString +getSaplingPathAnchor hex p = + withPureBorshVarBuffer $ rustWrapperReadSaplingPathAnchor p (hexBytes hex) + getSaplingFrontier :: SaplingCommitmentTree -> Maybe SaplingFrontier getSaplingFrontier tree = if sf_pos updatedTree > 1 diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 8b8a90b..1c55a73 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -611,6 +611,21 @@ newtype SaplingCommitmentTree = SaplingCommitmentTree { sapTree :: HexString } deriving (Eq, Prelude.Show, Read) +data SaplingRawTree = SaplingRawTree + { srt_left :: !HexString + , srt_right :: !HexString + , srt_parents :: ![HexString] + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct SaplingRawTree + +data SaplingTree = SaplingTree + { st_left :: !(Maybe HexString) + , st_right :: !(Maybe HexString) + , st_parents :: ![Maybe HexString] + } deriving (Eq, Prelude.Show, Read) + data SaplingFrontier = SaplingFrontier { sf_pos :: !Int64 , sf_leaf :: !HexString @@ -797,7 +812,7 @@ data TransparentTxSpend = TransparentTxSpend data SaplingTxSpend = SaplingTxSpend { ss_sk :: !BS.ByteString , ss_note :: !DecodedNote - , ss_iw :: !BS.ByteString + , ss_iw :: !MerklePath } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) @@ -806,7 +821,7 @@ data SaplingTxSpend = SaplingTxSpend data OrchardTxSpend = OrchardTxSpend { ss_sk :: !BS.ByteString , ss_note :: !DecodedNote - , ss_iw :: !BS.ByteString + , ss_iw :: !MerklePath } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index ee0a0d9..03db2b5 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -123,8 +123,8 @@ readZebraTransaction hex = rawTx = (withPureBorshVarBuffer . rustWrapperTxRead) $ hexBytes hex createTransaction :: - Maybe SaplingCommitmentTree -- ^ to obtain the Sapling anchor - -> Maybe OrchardCommitmentTree -- ^ to obtain the Orchard anchor + HexString -- ^ to obtain the Sapling anchor + -> HexString -- ^ to obtain the Orchard anchor -> [TransparentTxSpend] -- ^ the list of transparent notes to spend -> [SaplingTxSpend] -- ^ the list of Sapling notes to spend -> [OrchardTxSpend] -- ^ the list of Orchard notes to spend @@ -137,12 +137,8 @@ createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing znet bh bui txResult <- withBorshBufferOfInitSize 51200 $ rustWrapperCreateTx - (case sapAnchor of - Nothing -> "0" - Just sA -> toBytes $ sapTree sA) - (case orchAnchor of - Nothing -> "0" - Just oA -> toBytes $ orchTree oA) + (hexBytes sapAnchor) + (hexBytes orchAnchor) tSpend sSpend oSpend diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 3216af8..c1d1d82 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.4.0 +version: 0.7.5.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain -- 2.34.1