From e7050f03c0423a3f0682aa2d26bbb06683630d67 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 20 Mar 2024 11:13:02 -0500 Subject: [PATCH 1/8] Upgrade Zebra call --- CHANGELOG.md | 7 ++++++- src/ZcashHaskell/Sapling.hs | 2 +- src/ZcashHaskell/Types.hs | 6 +++--- src/ZcashHaskell/Utils.hs | 20 +++++++++++++++++--- zcash-haskell.cabal | 3 ++- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c81c2..ad3ee7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,13 @@ 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.5.1.0] -## [Unreleased] +### Changed + +- Modified the `makeZebraCall` function to handle errors explicitly + +## [0.5.0.1] ### Added diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index c744516..fb1c459 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -126,7 +126,7 @@ genSaplingPaymentAddress i extspk = -- | Generate an internal Sapling address genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingReceiver genSaplingInternalAddress sk = - if BS.length res > 0 + if BS.length res == 43 then Just $ SaplingReceiver res else Nothing where diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 091d661..32f4e57 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -44,7 +44,7 @@ import Haskoin.Crypto.Keys.Extended (XPrvKey) -- -- | A seed for generating private keys newtype Seed = - Seed C.ByteString + Seed BS.ByteString deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) @@ -55,7 +55,7 @@ instance ToBytes Seed where -- | A mnemonic phrase used to derive seeds newtype Phrase = - Phrase BS.ByteString + Phrase C.ByteString deriving stock (Eq, Prelude.Show, GHC.Generic, Read) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) @@ -191,7 +191,7 @@ data BlockResponse = BlockResponse { bl_confirmations :: !Integer -- ^ Block confirmations , bl_height :: !Integer -- ^ Block height , bl_time :: !Integer -- ^ Block time - , bl_txs :: ![T.Text] -- ^ List of transaction IDs in the block + , bl_txs :: ![HexString] -- ^ List of transaction IDs in the block } deriving (Prelude.Show, Eq) instance FromJSON BlockResponse where diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index f6f1ceb..702e453 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -23,12 +23,14 @@ import C.Zcash , rustWrapperF4Jumble , rustWrapperF4UnJumble ) +import Control.Exception (try) import Control.Monad.IO.Class import Data.Aeson import qualified Data.ByteString as BS import qualified Data.Text as T import qualified Data.Text.Encoding as E import Foreign.Rust.Marshall.Variable +import Network.HTTP.Client (HttpException(..)) import Network.HTTP.Simple import ZcashHaskell.Types @@ -74,12 +76,12 @@ makeZcashCall username password m p = do -- | Make a Zebra RPC call makeZebraCall :: - (MonadIO m, FromJSON a) + FromJSON a => T.Text -- ^ Hostname for `zebrad` -> Int -- ^ Port for `zebrad` -> T.Text -- ^ RPC method to call -> [Data.Aeson.Value] -- ^ List of parameters - -> m (Response a) + -> IO (Either String a) makeZebraCall host port m params = do let payload = RpcCall "2.0" "zh" m params let myRequest = @@ -87,4 +89,16 @@ makeZebraCall host port m params = do setRequestPort port $ setRequestHost (E.encodeUtf8 host) $ setRequestMethod "POST" defaultRequest - httpJSON myRequest + r <- + try $ httpJSON myRequest :: FromJSON a1 => + IO (Either HttpException (Response (RpcResponse a1))) + case r of + Left ex -> return $ Left $ show ex + Right res -> do + let zebraResp = getResponseBody res + case err zebraResp of + Just zErr -> return $ Left $ T.unpack $ emessage zErr + Nothing -> + case result zebraResp of + Nothing -> return $ Left "Empty response from Zebra" + Just zR -> return $ Right zR diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 646d810..05ac71b 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.5.0.1 +version: 0.5.1.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain @@ -53,6 +53,7 @@ library , generics-sop , hexstring >=0.12.1 , http-conduit + , http-client , memory , text , haskoin-core From 517b736c9a67a9f54546cce04f589b0fdfa9c8d0 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 20 Mar 2024 14:16:12 -0500 Subject: [PATCH 2/8] Allow for missing `result` in RPC response --- CHANGELOG.md | 1 + src/ZcashHaskell/Types.hs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad3ee7b..fef5e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Modified the `makeZebraCall` function to handle errors explicitly +- Modified the RPC response to handle missing `result` field ## [0.5.0.1] diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 32f4e57..182ddc1 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -168,7 +168,7 @@ instance (FromJSON r) => FromJSON (RpcResponse r) where withObject "RpcResponse" $ \obj -> do e <- obj .:? "error" i <- obj .: "id" - r <- obj .: "result" + r <- obj .:? "result" pure $ MakeRpcResponse e i r -- | A type to model the errors from the Zcash RPC From 69bce58345bfb9b0bf2a30d1cae0b834a769d66f Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Thu, 21 Mar 2024 12:52:45 -0500 Subject: [PATCH 3/8] Improve exception handling of Zebra calls --- src/ZcashHaskell/Utils.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index 702e453..39296e9 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -23,7 +23,7 @@ import C.Zcash , rustWrapperF4Jumble , rustWrapperF4UnJumble ) -import Control.Exception (try) +import Control.Exception (SomeException(..), try) import Control.Monad.IO.Class import Data.Aeson import qualified Data.ByteString as BS @@ -91,7 +91,7 @@ makeZebraCall host port m params = do setRequestMethod "POST" defaultRequest r <- try $ httpJSON myRequest :: FromJSON a1 => - IO (Either HttpException (Response (RpcResponse a1))) + IO (Either SomeException (Response (RpcResponse a1))) case r of Left ex -> return $ Left $ show ex Right res -> do From 9c8a851eadb860ee71092c5af3c77945109bf58f Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Thu, 21 Mar 2024 15:12:22 -0500 Subject: [PATCH 4/8] Implement Sapling spends --- CHANGELOG.md | 4 ++++ src/ZcashHaskell/Sapling.hs | 6 ++++-- src/ZcashHaskell/Types.hs | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fef5e74..999e019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.1.0] +### Added + +- Functionality to capture Sapling Spends + ### Changed - Modified the `makeZebraCall` function to handle errors explicitly diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index fb1c459..2dc7f49 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -90,11 +90,13 @@ instance FromJSON RawTxResponse where ht <- obj .: "height" c <- obj .: "confirmations" b <- obj .: "blocktime" + sSpend <- obj .: "vShieldedSpend" case o of - Nothing -> pure $ RawTxResponse i h (getShieldedOutputs h) [] ht c b + Nothing -> + pure $ RawTxResponse i h sSpend (getShieldedOutputs h) [] ht c b Just o' -> do a <- o' .: "actions" - pure $ RawTxResponse i h (getShieldedOutputs h) a ht c b + pure $ RawTxResponse i h sSpend (getShieldedOutputs h) a ht c b -- | Attempts to obtain a sapling SpendingKey using a HDSeed genSaplingSpendingKey :: Seed -> CoinType -> Int -> Maybe SaplingSpendingKey diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 182ddc1..e7f68b3 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -207,6 +207,7 @@ instance FromJSON BlockResponse where data RawTxResponse = RawTxResponse { rt_id :: !HexString , rt_hex :: !HexString + , rt_shieldedSpends :: ![ShieldedSpend] , rt_shieldedOutputs :: ![BS.ByteString] , rt_orchardActions :: ![OrchardAction] , rt_blockheight :: !Integer @@ -283,6 +284,30 @@ newtype SaplingReceiver = instance ToBytes SaplingReceiver where getBytes (SaplingReceiver s) = s +-- | Type to represent a Sapling Shielded Spend as provided by the @getrawtransaction@ RPC method +data ShieldedSpend = ShieldedSpend + { sp_cv :: !HexString + , sp_anchor :: !HexString + , sp_nullifier :: !HexString + , sp_rk :: !HexString + , sp_proof :: !HexString + , sp_auth :: !HexString + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct ShieldedSpend + +instance FromJSON ShieldedSpend where + parseJSON = + withObject "ShieldedSpend" $ \obj -> do + cv <- obj .: "cv" + anchor <- obj .: "anchor" + nullifier <- obj .: "nullifier" + rk <- obj .: "rk" + p <- obj .: "proof" + sig <- obj .: "spendAuthSig" + pure $ ShieldedSpend cv anchor nullifier rk p sig + -- | Type to represent a Sapling Shielded Output as provided by the @getrawtransaction@ RPC method of @zcashd@. data ShieldedOutput = ShieldedOutput { s_cv :: !HexString -- ^ Value commitment to the input note From 5b6ce3f29b718cb7ff63f768ab67201cfda48677 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Thu, 21 Mar 2024 19:27:09 -0500 Subject: [PATCH 5/8] Account for missing block time field --- src/ZcashHaskell/Types.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index e7f68b3..612410e 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -7,7 +7,7 @@ -- Copyright : 2022-2024 Vergara Technologies -- License : MIT -- --- Maintainer : pitmut@vergara.tech +-- Maintainer : pitmutt@vergara.tech -- Stability : experimental -- Portability : unknown -- @@ -31,6 +31,7 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C import Data.HexString import Data.Int +import Data.Maybe (fromMaybe) import Data.Structured import qualified Data.Text as T import qualified Data.Text.Encoding as E @@ -199,9 +200,9 @@ instance FromJSON BlockResponse where withObject "BlockResponse" $ \obj -> do c <- obj .: "confirmations" h <- obj .: "height" - t <- obj .: "time" + t <- obj .:? "time" txs <- obj .: "tx" - pure $ BlockResponse c h t txs + pure $ BlockResponse c h (fromMaybe 0 t) txs -- | Type to represent response from the `zcashd` RPC `getrawtransaction` data RawTxResponse = RawTxResponse From b0df0480c5f5e71ba30d23927cae8f6a06b976f9 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 26 Mar 2024 09:50:49 -0500 Subject: [PATCH 6/8] Implement Transaction hex deserialization --- librustzcash-wrapper/src/lib.rs | 112 +++++++++++++++++++++++++++++++- src/C/Zcash.chs | 7 ++ src/ZcashHaskell/Types.hs | 101 +++++++++++++++++++++++----- src/ZcashHaskell/Utils.hs | 19 ++++-- test/Spec.hs | 7 ++ zebrablock.json | 20 ++++++ zebrahexblock.json | 4 ++ zebratx.json | 8 +++ 8 files changed, 253 insertions(+), 25 deletions(-) create mode 100644 zebrablock.json create mode 100644 zebrahexblock.json create mode 100644 zebratx.json diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 2451d27..d6ee532 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -31,9 +31,17 @@ use zcash_primitives::{ sapling::DiversifierKey }, zip339::{Count, Mnemonic}, - transaction::components::sapling::{ - GrothProofBytes, - OutputDescription + transaction::components::{ + transparent::{ + Bundle as TransparentBundle, + TxIn, + TxOut, + Authorized + }, + sapling::{ + GrothProofBytes, + OutputDescription + } }, sapling::{ PaymentAddress, @@ -220,6 +228,81 @@ impl Hua { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Htx { + txid: Vec, + locktime: u32, + expiry: u32, + t_bundle: HTBundle +} + +impl ToHaskell for Htx { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HTBundle { + vin: Vec, + vout: Vec, + coinbase: bool +} + +impl ToHaskell for HTBundle { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTBundle { + pub fn from_bundle(b: &TransparentBundle) -> HTBundle { + HTBundle { vin: b.vin.iter().map(HTxIn::pack).collect() , vout: b.vout.iter().map(HTxOut::pack).collect(), coinbase: b.is_coinbase()} + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HTxIn { + outpoint: u32, + script: Vec, + sequence: u32 +} + +impl ToHaskell for HTxIn { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTxIn { + pub fn pack(t: &TxIn) -> HTxIn { + return HTxIn { outpoint: t.prevout.n(), script: t.script_sig.0.clone(), sequence: t.sequence} + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HTxOut { + amt: i64, + script: Vec +} + +impl ToHaskell for HTxOut { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTxOut { + pub fn pack(t: &TxOut) -> HTxOut { + return HTxOut { amt: i64::from_le_bytes(t.value.to_i64_le_bytes()) , script: t.script_pubkey.0.clone() } + } +} + #[derive(BorshSerialize, BorshDeserialize)] pub struct Hufvk { net: u8, @@ -555,6 +638,29 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( } } + +#[no_mangle] +pub extern "C" fn rust_wrapper_tx_read( + tx: *const u8, + tx_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tx_input: Vec = marshall_from_haskell_var(tx, tx_len, RW); + let mut tx_reader = Cursor::new(tx_input); + let parsed_tx = Transaction::read(&mut tx_reader, Nu5); + match parsed_tx { + Ok(t) => { + let h = Htx {txid: t.txid().as_ref().to_vec(), locktime: t.lock_time(), expiry: u32::from(t.expiry_height()), t_bundle: HTBundle::from_bundle(t.transparent_bundle().unwrap()) }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let h0 = Htx {txid: vec![0], locktime: 0, expiry: 0, t_bundle: HTBundle {vin: vec![HTxIn {outpoint: 0, script: vec![0], sequence: 0}], vout: vec![HTxOut {amt: 0, script: vec![0]}], coinbase: true} }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_tx_parse( tx: *const u8, diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 19df3c3..7bf023f 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -123,6 +123,13 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_tx_read as rustWrapperTxRead + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer RawZebraTx'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_gen_seed_phrase as rustWrapperGenSeedPhrase { getVarBuffer `Buffer Phrase'& } -> `()' #} diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 612410e..d22a8a7 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -16,6 +16,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralisedNewtypeDeriving #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE UndecidableInstances #-} @@ -40,6 +41,7 @@ import qualified GHC.Generics as GHC import qualified Generics.SOP as SOP import Haskoin.Address (Address) import Haskoin.Crypto.Keys.Extended (XPrvKey) +import qualified Haskoin.Transaction.Common as H -- * General -- @@ -89,6 +91,19 @@ data ZcashNet type AccountId = Int +-- | Function to get the Base58 prefix for encoding a 'TransparentAddress' +getTransparentPrefix :: ZcashNet -> TransparentType -> (Word8, Word8) +getTransparentPrefix n t = + case t of + P2SH -> + case n of + MainNet -> (0x1c, 0xbd) + _ -> (0x1c, 0xba) + P2PKH -> + case n of + MainNet -> (0x1c, 0xb8) + _ -> (0x1d, 0x25) + -- ** Constants -- | Type for coin types on the different networks data CoinType @@ -104,7 +119,23 @@ getValue c = TestNetCoin -> 1 RegTestNetCoin -> 1 --- | Constants for Sapling Human-readable part +-- | A Zcash transaction +data Transaction = Transaction + { tx_id :: !HexString + , tx_height :: !Int + , tx_conf :: !Int + , tx_expiry :: !Int + , tx_transpBundle :: !(Maybe TransparentBundle) + } deriving (Prelude.Show, Eq, Read) + +-- | The transparent portion of a Zcash transaction +data TransparentBundle = TransparentBundle + { tb_vin :: ![H.TxIn] + , tb_vout :: ![H.TxOut] + , tb_coinbase :: !Bool + } deriving (Eq, Prelude.Show, Read) + +-- *** Constants for Sapling Human-readable part sapExtSpendingKeyHrp = "secret-extended-key-main" :: String sapExtFullViewingKeyHrp = "zxviews" :: String @@ -117,7 +148,7 @@ sapTestExtFullViewingKeyHrp = "zxviewtestsapling" :: String sapTestPaymentAddressHrp = "ztestsapling" :: String --- | Constants for Unified Human-readable part +-- *** Constants for Unified Human-readable part uniPaymentAddressHrp = "u" :: T.Text uniFullViewingKeyHrp = "uview" :: T.Text @@ -130,19 +161,6 @@ uniTestFullViewingKeyHrp = "uviewtest" :: T.Text uniTestIncomingViewingKeyHrp = "uivktest" :: T.Text --- | Function to get the Base58 prefix for encoding a 'TransparentAddress' -getTransparentPrefix :: ZcashNet -> TransparentType -> (Word8, Word8) -getTransparentPrefix n t = - case t of - P2SH -> - case n of - MainNet -> (0x1c, 0xbd) - _ -> (0x1c, 0xba) - P2PKH -> - case n of - MainNet -> (0x1c, 0xb8) - _ -> (0x1d, 0x25) - -- * RPC -- | A type to model Zcash RPC calls data RpcCall = RpcCall @@ -217,6 +235,41 @@ data RawTxResponse = RawTxResponse } deriving (Prelude.Show, Eq, Read) -- ** `zebrad` +data ZebraTxResponse = ZebraTxResponse + { ztr_blockheight :: !Int + , ztr_conf :: !Int + , ztr_hex :: !HexString + } deriving (Prelude.Show, Eq, Read) + +instance FromJSON ZebraTxResponse where + parseJSON = + withObject "ZebraTxResponse" $ \obj -> do + hex <- obj .: "hex" + height <- obj .: "height" + c <- obj .: "confirmations" + pure $ ZebraTxResponse height c hex + +-- | Type to represent a raw deserialized Zebra transaction +data RawZebraTx = RawZebraTx + { zt_id :: !HexString + , zt_locktime :: !Word32 + , zt_expiry :: !Word32 + , zt_tBundle :: !RawTBundle + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawZebraTx + +-- | Type for a raw deserialized Zebra transparent bundle +data RawTBundle = RawTBundle + { ztb_vin :: ![RawTxIn] + , ztb_vout :: ![RawTxOut] + , ztb_coinbase :: !Bool + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTBundle + -- | Type for the response from the `zebrad` RPC method `getinfo` data ZebraGetInfo = ZebraGetInfo { zgi_build :: !T.Text @@ -268,6 +321,24 @@ data TransparentAddress = TransparentAddress , ta_bytes :: !HexString } deriving (Eq, Prelude.Show, Read) +-- | Wrapper types for transparent elements +data RawTxIn = RawTxIn + { rti_outpoint :: !Word32 + , rti_script :: !BS.ByteString + , rti_seq :: !Word32 + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTxIn + +data RawTxOut = RawTxOut + { rto_amt :: !Word64 + , rto_script :: !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 RawTxOut + -- * Sapling -- | A spending key for Sapling newtype SaplingSpendingKey = diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index 39296e9..1407c79 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -22,11 +22,13 @@ import C.Zcash , rustWrapperBech32Encode , rustWrapperF4Jumble , rustWrapperF4UnJumble + , rustWrapperTxRead ) import Control.Exception (SomeException(..), try) import Control.Monad.IO.Class import Data.Aeson import qualified Data.ByteString as BS +import Data.HexString (HexString(..)) import qualified Data.Text as T import qualified Data.Text.Encoding as E import Foreign.Rust.Marshall.Variable @@ -34,13 +36,7 @@ import Network.HTTP.Client (HttpException(..)) import Network.HTTP.Simple import ZcashHaskell.Types -import Foreign.C.Types -import Foreign.Marshal.Array (allocaArray, peekArray) -import Foreign.Ptr (Ptr) - -import Data.Word - --- | +-- * Utility functions -- | Decode the given bytestring using Bech32 decodeBech32 :: BS.ByteString -> RawData decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode @@ -57,6 +53,7 @@ f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble f4UnJumble :: BS.ByteString -> BS.ByteString f4UnJumble = withPureBorshVarBuffer . rustWrapperF4UnJumble +-- * Node interaction -- | Make a Zcash RPC call makeZcashCall :: (MonadIO m, FromJSON a) @@ -102,3 +99,11 @@ makeZebraCall host port m params = do case result zebraResp of Nothing -> return $ Left "Empty response from Zebra" Just zR -> return $ Right zR + +readZebraTransaction :: HexString -> Maybe RawZebraTx +readZebraTransaction hex = + if BS.length (hexBytes $ zt_id rawTx) < 1 + then Nothing + else Just rawTx + where + rawTx = (withPureBorshVarBuffer . rustWrapperTxRead) $ hexBytes hex diff --git a/test/Spec.hs b/test/Spec.hs index 97c4a0c..f81c71a 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -71,6 +71,7 @@ import ZcashHaskell.Types , UnifiedAddress(..) , UnifiedFullViewingKey(..) , ZcashNet(..) + , ZebraTxResponse(..) , decodeHexText , getValue ) @@ -765,6 +766,12 @@ main = do let bscAdr = SaplingReceiver $ BS.pack cAdr let ca = genSaplingInternalAddress (SaplingSpendingKey $ BS.pack sk) fromMaybe (SaplingReceiver "") ca `shouldBe` bscAdr + describe "Zebra response processing: " $ do + it "Raw transaction" $ do + let h = + hexString + "0400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02" + readZebraTransaction h `shouldNotBe` Nothing -- | Properties prop_PhraseLength :: Property diff --git a/zebrablock.json b/zebrablock.json new file mode 100644 index 0000000..5665f92 --- /dev/null +++ b/zebrablock.json @@ -0,0 +1,20 @@ +{ + "result": { + "hash": "0041ee9cb0e256a73c92bb72d830143c402ea350152f56f19f74d23cf51418fb", + "confirmations": 3583, + "height": 2767099, + "tx": [ + "d169ec3eda57dc750edfc1aa6b8ffb4ed2065780bfd5964de34b529503ec372f", + "987fcdb9bd37cbb5b205a8336de60d043f7028bebaa372828d81f3da296c7ef9" + ], + "trees": { + "sapling": { + "size": 129349 + }, + "orchard": { + "size": 39382 + } + } + }, + "id": 123 +} diff --git a/zebrahexblock.json b/zebrahexblock.json new file mode 100644 index 0000000..19d6b90 --- /dev/null +++ b/zebrahexblock.json @@ -0,0 +1,4 @@ +{ + "result": "040000006deacb36066fbbf99f00436c5a384e885c0cecff393b922ccfb22ed81b9e3600722039ae03cfdbee344a18774168795a936a720db144408a9abd92c55eb4a276f1b96e93c15bc2943a1f2483e466c456d2ea22ad82eea3d5788dbb5448bcb30c33d0fd658002451f6c005c8fa5b5d0cb7a24d326de3fd34422e22718a0c5e23972800292ed830000fd4005022b23ffe50b150fe854961e44e5d752975857fbc5026bc06a7550448e858e71dc7d927b44a04a325df027973f510de1ebb56327d7ed086b7419ff71db198940760dcede94a775fd67f515836ec4caa7d17bdc6e0ec8924c9dc74945496e673d104b85d35df91ec33942c84b7404579023b24d78b30bcde7563740f3a10a159918daafa18d955b0bb74c94782195ff5195936b21e6ec853a8ef269b21ac325675445b1815dfab18904ad6e2ad19ec8a753d1f0fd3b9171e0ea29f274bb261b028508d984fcdb9ed3df6e77ba3eb038d606d517b7461ebee25b57de47b2f2dde11c7504bacd271f2f44e405f79faaf99962145aa537b2b1f5f4f55ea30657f48ae51f0aff8f1bc1669311607cf941717e561b5e7e44bd48adc9aafba49d947deabe07e475c52e3eb13d485e60790db1a04540b4dfe992578b9758ed42ad8bee87a60b71cc88357f2d654a368cad751455057ed219766206094c899100ffc2c1b306409931d608f99a4ba850403eeaf092ee8b47b24dfad9fe18520d3ecaa98d7cf915f270629ab3fa8214f439cd0a160df22be1d99d7a979bbbd1fcb2dfd8ccfafa1207691013f2a524e1613150e0c679ac5950cb08211defa839fc537d5d941c6141e9d724ff468b9759ec7eb825132a2a78f5b9c413ee9674b44dd25f8bb65d1e839a24b36ee0eca01129935e73437e4a08edc8a2ff97d10752867f0e46cbc0ba2ae7459a7f9fbf41da3ea7be2618caa57728aae1d378f2a408f9569da556d0fa630c183d94dd542052dbc9b52ef75585ca16cf5793240f61b15dca17bead2cc524856b36c12a74c7187480090e8c01f6c79c3cafe4e50eac4c89f6991df7ff040913e3f70058aaa123f015a86b433b8a621b555f4d1abacf785c477257cc3f43ba13220e29ef653ab6be34ce223455e9bb5d775a19200267035efe861ce55503545cc85db40f59cbb4f049154983509e19877d181c3ffeff7c8fc24f1ea3f22e2513de60f340a8c7d019b76175055bc87f94dfb6381a5319e237ab5d269e1fdbe7a9e38d7fcadbf32528e1f295f5fafd1bde5b09a24c0d04d2a401e8efd352ac51b24da627ed62c85c5fefdd1019b0f9ffd86a2c30ed03d2b1b3181e261f7b05accb95a8b1109b72dd1759b7158a1b7a2c913b0fd6b5ad1dba25740139070648641739985e9e82036e9941c15580832dc4258f425c44296cc7b0938910b29cbdfcdc48df4b753744297fc656397ff504c50ea77f0cf229eb25849621e417275bb58f0b773a173d69bdf1339438b5cd5a44871436b8f56e37b059ae06ae61517e2e2857d5b422846f15d440d46fe8be4217836c545d56cfa75d46539cd0b71311e40abe30c21766ca99310bb5bab07061d69b250fc99f855c7b1218275dbe69d2d1b0aba51815126e08f68553d99de9078aad6a732b16618578127604aa0c565b2a5f0f1f15f9916ee72ffb43ace3a615bf74c461ac150fc5fa3537c78537d3aec997a1b426df2db27ad4b2dad1d34cabe61c16a9b9118ec017b650c4fc1669ecb725330c1849136e503d719b081157704377b8c778cf79031e1d25d98f4d3eb537aa564191404132231db19a64102a4c37395a86c6d557715379e39b6ec3bd7e5e4611ceeb45198e577f3efd92dea57f694ccbb20b0b44082a22cb61c758c063c862156ba6093aeed8986e1f0afd3a26f46022d515af226bdac94fb0f61f57708c2285c39c8df04a65cbdbb3fa06a53482131ed1bd8a66ed9fcdb7a995a37de76a67d75932deb989fdf565084b10954caca8919dc125483d722a02207838ccb94bb9df367e23ae37efe8c55d23bdf9cdb31d5defbc0fe25fd6dd1bf53bf30112c3babb9161d42cd5a58330cc4a44c999b2bfc701a45e7ec3b5ef493c9e1887020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603fb382a011cffffffff0468b6e60e000000001976a91401cb2de6a35b807a6e75d7fbfcc8107b2b9d39f488ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000fb382a0000000000000000000000000400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02", + "id": 123 +} diff --git a/zebratx.json b/zebratx.json new file mode 100644 index 0000000..83f6b4b --- /dev/null +++ b/zebratx.json @@ -0,0 +1,8 @@ +{ + "result": { + "hex": "0400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02", + "height": 2767099, + "confirmations": 3582 + }, + "id": 123 +} From f593fefd7f80620137b99209b27fce22edeecccb Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 26 Mar 2024 15:37:04 -0500 Subject: [PATCH 7/8] Add types for low-level transparent components --- librustzcash-wrapper/src/lib.rs | 42 ++++++++++++++++++++++++++++----- src/ZcashHaskell/Types.hs | 37 +++++++++++++++++++++++++++-- zcash-haskell.cabal | 1 + 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index d6ee532..e74beb3 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -36,6 +36,7 @@ use zcash_primitives::{ Bundle as TransparentBundle, TxIn, TxOut, + OutPoint, Authorized }, sapling::{ @@ -246,6 +247,7 @@ impl ToHaskell for Htx { #[derive(BorshSerialize, BorshDeserialize)] pub struct HTBundle { + empty: bool, vin: Vec, vout: Vec, coinbase: bool @@ -260,13 +262,13 @@ impl ToHaskell for HTBundle { impl HTBundle { pub fn from_bundle(b: &TransparentBundle) -> HTBundle { - HTBundle { vin: b.vin.iter().map(HTxIn::pack).collect() , vout: b.vout.iter().map(HTxOut::pack).collect(), coinbase: b.is_coinbase()} + HTBundle {empty: false, vin: b.vin.iter().map(HTxIn::pack).collect() , vout: b.vout.iter().map(HTxOut::pack).collect(), coinbase: b.is_coinbase()} } } #[derive(BorshSerialize, BorshDeserialize)] pub struct HTxIn { - outpoint: u32, + outpoint: Houtpoint, script: Vec, sequence: u32 } @@ -280,7 +282,7 @@ impl ToHaskell for HTxIn { impl HTxIn { pub fn pack(t: &TxIn) -> HTxIn { - return HTxIn { outpoint: t.prevout.n(), script: t.script_sig.0.clone(), sequence: t.sequence} + return HTxIn { outpoint: Houtpoint::pack(&t.prevout), script: t.script_sig.0.clone(), sequence: t.sequence} } } @@ -303,6 +305,25 @@ impl HTxOut { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Houtpoint { + hash: Vec, + index: u32 +} + +impl ToHaskell for Houtpoint { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl Houtpoint { + pub fn pack(o: &OutPoint) -> Houtpoint { + return Houtpoint {hash: o.hash().to_vec() , index: o.n() } + } +} + #[derive(BorshSerialize, BorshDeserialize)] pub struct Hufvk { net: u8, @@ -651,11 +672,20 @@ pub extern "C" fn rust_wrapper_tx_read( let parsed_tx = Transaction::read(&mut tx_reader, Nu5); match parsed_tx { Ok(t) => { - let h = Htx {txid: t.txid().as_ref().to_vec(), locktime: t.lock_time(), expiry: u32::from(t.expiry_height()), t_bundle: HTBundle::from_bundle(t.transparent_bundle().unwrap()) }; - marshall_to_haskell_var(&h, out, out_len, RW); + let tb = t.transparent_bundle(); + match tb { + Some(my_tb) => { + let h = Htx {txid: t.txid().as_ref().to_vec(), locktime: t.lock_time(), expiry: u32::from(t.expiry_height()), t_bundle: HTBundle::from_bundle(my_tb) }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + None => { + let h0 = Htx {txid: t.txid().as_ref().to_vec(), locktime: t.lock_time(), expiry: u32::from(t.expiry_height()), t_bundle: HTBundle {empty: true, vin: vec![HTxIn {outpoint: Houtpoint {hash: vec![0], index: 0}, script: vec![0], sequence: 0}], vout: vec![HTxOut {amt: 0, script: vec![0]}], coinbase: true} }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } }, Err(_e) => { - let h0 = Htx {txid: vec![0], locktime: 0, expiry: 0, t_bundle: HTBundle {vin: vec![HTxIn {outpoint: 0, script: vec![0], sequence: 0}], vout: vec![HTxOut {amt: 0, script: vec![0]}], coinbase: true} }; + let h0 = Htx {txid: vec![0], locktime: 0, expiry: 0, t_bundle: HTBundle {empty: true, vin: vec![HTxIn {outpoint: Houtpoint {hash: vec![0], index: 0}, script: vec![0], sequence: 0}], vout: vec![HTxOut {amt: 0, script: vec![0]}], coinbase: true} }; marshall_to_haskell_var(&h0, out, out_len, RW); } } diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index d22a8a7..91ef4a3 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -30,6 +30,7 @@ import Data.Aeson import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C +import qualified Data.ByteString.Lazy.UTF8 as US import Data.HexString import Data.Int import Data.Maybe (fromMaybe) @@ -135,6 +136,29 @@ data TransparentBundle = TransparentBundle , tb_coinbase :: !Bool } deriving (Eq, Prelude.Show, Read) +-- | Read a raw transparent bundle into the Haskell type +fromRawTBundle :: RawTBundle -> Maybe TransparentBundle +fromRawTBundle rtb = + if ztb_empty rtb + then Nothing + else Just $ + TransparentBundle + (map fromRawTxIn $ ztb_vin rtb) + (map fromRawTxOut $ ztb_vout rtb) + (ztb_coinbase rtb) + +fromRawTxIn :: RawTxIn -> H.TxIn +fromRawTxIn t = + H.TxIn + (H.OutPoint + (read $ US.toString $ C.fromStrict $ rop_hash $ rti_outpoint t) + (rop_n $ rti_outpoint t)) + (rti_script t) + (rti_seq t) + +fromRawTxOut :: RawTxOut -> H.TxOut +fromRawTxOut t = H.TxOut (rto_amt t) (rto_script t) + -- *** Constants for Sapling Human-readable part sapExtSpendingKeyHrp = "secret-extended-key-main" :: String @@ -262,7 +286,8 @@ data RawZebraTx = RawZebraTx -- | Type for a raw deserialized Zebra transparent bundle data RawTBundle = RawTBundle - { ztb_vin :: ![RawTxIn] + { ztb_empty :: !Bool + , ztb_vin :: ![RawTxIn] , ztb_vout :: ![RawTxOut] , ztb_coinbase :: !Bool } deriving stock (Eq, Prelude.Show, GHC.Generic) @@ -323,7 +348,7 @@ data TransparentAddress = TransparentAddress -- | Wrapper types for transparent elements data RawTxIn = RawTxIn - { rti_outpoint :: !Word32 + { rti_outpoint :: !RawOutPoint , rti_script :: !BS.ByteString , rti_seq :: !Word32 } deriving stock (Eq, Prelude.Show, GHC.Generic) @@ -339,6 +364,14 @@ data RawTxOut = RawTxOut deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTxOut +data RawOutPoint = RawOutPoint + { rop_hash :: !BS.ByteString + , rop_n :: !Word32 + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawOutPoint + -- * Sapling -- | A spending key for Sapling newtype SaplingSpendingKey = diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 05ac71b..15d27f9 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -58,6 +58,7 @@ library , text , haskoin-core , secp256k1-haskell + , utf8-string build-tool-depends: c2hs:c2hs default-language: Haskell2010 From 0fb02b2514ca21af8a65533bdf12fdcc42a1d3b0 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 27 Mar 2024 09:00:00 -0500 Subject: [PATCH 8/8] Correct parsing of OutPoint --- src/ZcashHaskell/Types.hs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 91ef4a3..c4be9ed 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -31,9 +31,10 @@ import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Lazy.UTF8 as US +import qualified Data.ByteString.Short as BS (ShortByteString, toShort) import Data.HexString import Data.Int -import Data.Maybe (fromMaybe) +import Data.Maybe (fromJust, fromMaybe) import Data.Structured import qualified Data.Text as T import qualified Data.Text.Encoding as E @@ -41,6 +42,7 @@ import Data.Word import qualified GHC.Generics as GHC import qualified Generics.SOP as SOP import Haskoin.Address (Address) +import qualified Haskoin.Crypto.Hash as H (Hash256(..)) import Haskoin.Crypto.Keys.Extended (XPrvKey) import qualified Haskoin.Transaction.Common as H @@ -151,7 +153,9 @@ fromRawTxIn :: RawTxIn -> H.TxIn fromRawTxIn t = H.TxIn (H.OutPoint - (read $ US.toString $ C.fromStrict $ rop_hash $ rti_outpoint t) + ((fromJust . + H.hexToTxHash . E.decodeUtf8Lenient . rop_hash . rti_outpoint) + t) (rop_n $ rti_outpoint t)) (rti_script t) (rti_seq t)