2024-01-18 18:55:23 +00:00
|
|
|
-- Copyright 2022-2024 Vergara Technologies LLC
|
|
|
|
--
|
|
|
|
-- This file is part of Zcash-Haskell.
|
|
|
|
--
|
2023-12-20 20:03:42 +00:00
|
|
|
-- |
|
|
|
|
-- Module : ZcashHaskell.Sapling
|
|
|
|
-- Copyright : 2022-2024 Vergara Technologies
|
2024-01-18 18:55:23 +00:00
|
|
|
-- License : MIT
|
2023-12-20 20:03:42 +00:00
|
|
|
--
|
2024-01-18 18:55:23 +00:00
|
|
|
-- Maintainer : pitmutt@vergara.tech
|
2023-12-20 20:03:42 +00:00
|
|
|
-- Stability : experimental
|
|
|
|
-- Portability : unknown
|
|
|
|
--
|
|
|
|
-- Functions to interact with the Sapling shielded pool of the Zcash blockchain.
|
|
|
|
--
|
2024-01-18 18:55:23 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
2023-06-14 15:53:29 +00:00
|
|
|
module ZcashHaskell.Sapling where
|
2023-05-04 20:26:49 +00:00
|
|
|
|
2023-06-14 15:53:29 +00:00
|
|
|
import C.Zcash
|
2024-04-26 00:56:29 +00:00
|
|
|
( rustWrapperDecodeSaplingAddress
|
|
|
|
, rustWrapperIsShielded
|
2024-04-10 12:06:04 +00:00
|
|
|
, rustWrapperReadSaplingCommitmentTree
|
2024-04-11 21:01:29 +00:00
|
|
|
, rustWrapperReadSaplingPosition
|
|
|
|
, rustWrapperReadSaplingWitness
|
2023-06-14 15:53:29 +00:00
|
|
|
, rustWrapperSaplingCheck
|
2024-03-15 15:11:27 +00:00
|
|
|
, rustWrapperSaplingChgPaymentAddress
|
2024-04-08 17:54:05 +00:00
|
|
|
, rustWrapperSaplingDecodeEsk
|
2023-08-22 20:05:40 +00:00
|
|
|
, rustWrapperSaplingNoteDecode
|
2024-03-10 12:47:26 +00:00
|
|
|
, rustWrapperSaplingPaymentAddress
|
2024-03-05 13:36:45 +00:00
|
|
|
, rustWrapperSaplingSpendingkey
|
2023-06-14 15:53:29 +00:00
|
|
|
, rustWrapperSaplingVkDecode
|
2023-09-27 15:37:53 +00:00
|
|
|
, rustWrapperTxParse
|
2024-04-26 00:56:29 +00:00
|
|
|
, rustWrapperUpdateSaplingWitness
|
2023-06-14 15:53:29 +00:00
|
|
|
)
|
2023-09-27 16:18:00 +00:00
|
|
|
import Data.Aeson
|
2023-05-04 20:26:49 +00:00
|
|
|
import qualified Data.ByteString as BS
|
2024-04-16 22:51:14 +00:00
|
|
|
import qualified Data.ByteString.Char8 as C
|
2024-04-10 12:06:04 +00:00
|
|
|
import Data.HexString (HexString(..), fromText, hexString, toBytes, toText)
|
2024-04-26 00:56:29 +00:00
|
|
|
import qualified Data.Text as T
|
2024-03-05 13:36:45 +00:00
|
|
|
import Data.Word
|
|
|
|
import Foreign.Rust.Marshall.Variable
|
2024-03-03 21:19:06 +00:00
|
|
|
( withPureBorshVarBuffer
|
|
|
|
, withPureBorshVarBuffer
|
|
|
|
)
|
2024-04-26 00:56:29 +00:00
|
|
|
import ZcashHaskell.Types
|
2024-04-16 22:51:14 +00:00
|
|
|
import ZcashHaskell.Utils (decodeBech32, encodeBech32, encodeBech32m)
|
2023-05-04 20:26:49 +00:00
|
|
|
|
|
|
|
-- | Check if given bytesting is a valid encoded shielded address
|
|
|
|
isValidShieldedAddress :: BS.ByteString -> Bool
|
|
|
|
isValidShieldedAddress = rustWrapperIsShielded
|
2023-06-14 14:55:52 +00:00
|
|
|
|
2024-04-29 15:27:45 +00:00
|
|
|
getShieldedOutputs :: HexString -> [ShieldedOutput]
|
2024-02-06 19:10:06 +00:00
|
|
|
getShieldedOutputs t = withPureBorshVarBuffer $ rustWrapperTxParse $ toBytes t
|
2023-09-27 16:18:00 +00:00
|
|
|
|
2024-04-08 17:54:05 +00:00
|
|
|
serializeShieldedOutput :: ShieldedOutput -> BS.ByteString
|
|
|
|
serializeShieldedOutput so =
|
|
|
|
hexBytes . fromText $
|
|
|
|
toText (s_cv so) <>
|
|
|
|
toText (s_cmu so) <>
|
|
|
|
toText (s_ephKey so) <>
|
|
|
|
toText (s_encCipherText so) <>
|
|
|
|
toText (s_outCipherText so) <> toText (s_proof so)
|
|
|
|
|
2023-06-14 14:55:52 +00:00
|
|
|
-- | Check if given bytestring is a valid Sapling viewing key
|
|
|
|
isValidSaplingViewingKey :: BS.ByteString -> Bool
|
2023-09-28 19:23:42 +00:00
|
|
|
isValidSaplingViewingKey k =
|
|
|
|
case hrp decodedKey of
|
|
|
|
"zxviews" -> rustWrapperSaplingVkDecode $ bytes decodedKey
|
|
|
|
_ -> False
|
|
|
|
where
|
|
|
|
decodedKey = decodeBech32 k
|
2023-06-14 15:53:29 +00:00
|
|
|
|
|
|
|
-- | Check if the given bytestring for the Sapling viewing key matches the second bytestring for the address
|
|
|
|
matchSaplingAddress :: BS.ByteString -> BS.ByteString -> Bool
|
|
|
|
matchSaplingAddress = rustWrapperSaplingCheck
|
2023-08-22 20:05:40 +00:00
|
|
|
|
2023-08-23 20:19:31 +00:00
|
|
|
-- | Attempt to decode the given raw tx with the given Sapling viewing key
|
2024-04-29 15:27:45 +00:00
|
|
|
decodeSaplingOutput :: BS.ByteString -> ShieldedOutput -> Maybe DecodedNote
|
2023-08-22 20:05:40 +00:00
|
|
|
decodeSaplingOutput key out =
|
|
|
|
case a_value decodedAction of
|
|
|
|
0 -> Nothing
|
|
|
|
_ -> Just decodedAction
|
|
|
|
where
|
|
|
|
decodedAction =
|
|
|
|
withPureBorshVarBuffer $ rustWrapperSaplingNoteDecode key out
|
2023-09-27 16:18:00 +00:00
|
|
|
|
|
|
|
instance FromJSON RawTxResponse where
|
|
|
|
parseJSON =
|
|
|
|
withObject "RawTxResponse" $ \obj -> do
|
|
|
|
i <- obj .: "txid"
|
2023-09-28 18:56:31 +00:00
|
|
|
o <- obj .:? "orchard"
|
2023-09-27 16:18:00 +00:00
|
|
|
h <- obj .: "hex"
|
2023-10-02 20:25:44 +00:00
|
|
|
ht <- obj .: "height"
|
|
|
|
c <- obj .: "confirmations"
|
|
|
|
b <- obj .: "blocktime"
|
2024-03-21 20:15:49 +00:00
|
|
|
sSpend <- obj .: "vShieldedSpend"
|
2023-09-28 18:56:31 +00:00
|
|
|
case o of
|
2024-03-21 20:15:49 +00:00
|
|
|
Nothing ->
|
|
|
|
pure $ RawTxResponse i h sSpend (getShieldedOutputs h) [] ht c b
|
2023-09-28 18:56:31 +00:00
|
|
|
Just o' -> do
|
|
|
|
a <- o' .: "actions"
|
2024-03-21 20:15:49 +00:00
|
|
|
pure $ RawTxResponse i h sSpend (getShieldedOutputs h) a ht c b
|
2024-03-10 12:47:26 +00:00
|
|
|
|
2024-04-08 17:54:05 +00:00
|
|
|
-- | Attempt to decode the given raw tx with the given Sapling spending key
|
|
|
|
decodeSaplingOutputEsk ::
|
|
|
|
SaplingSpendingKey
|
|
|
|
-> ShieldedOutput
|
|
|
|
-> ZcashNet
|
|
|
|
-> Scope
|
2024-04-12 18:15:41 +00:00
|
|
|
-> Integer
|
2024-04-08 17:54:05 +00:00
|
|
|
-> Maybe DecodedNote
|
2024-04-12 18:15:41 +00:00
|
|
|
decodeSaplingOutputEsk key out znet scope pos =
|
2024-04-08 17:54:05 +00:00
|
|
|
case a_value decodedAction of
|
|
|
|
0 -> Nothing
|
|
|
|
_ -> Just decodedAction
|
|
|
|
where
|
|
|
|
decodedAction =
|
|
|
|
withPureBorshVarBuffer $
|
|
|
|
rustWrapperSaplingDecodeEsk
|
|
|
|
(getBytes key)
|
2024-04-29 15:27:45 +00:00
|
|
|
out
|
2024-04-08 17:54:05 +00:00
|
|
|
(scope == External)
|
2024-04-16 00:58:26 +00:00
|
|
|
(znet == MainNet)
|
2024-04-12 18:15:41 +00:00
|
|
|
(fromIntegral pos)
|
2024-04-08 17:54:05 +00:00
|
|
|
|
2024-03-10 12:47:26 +00:00
|
|
|
-- | Attempts to obtain a sapling SpendingKey using a HDSeed
|
2024-03-13 17:50:39 +00:00
|
|
|
genSaplingSpendingKey :: Seed -> CoinType -> Int -> Maybe SaplingSpendingKey
|
|
|
|
genSaplingSpendingKey seed c i = do
|
2024-03-11 20:23:29 +00:00
|
|
|
if BS.length res == 169
|
2024-03-14 16:30:54 +00:00
|
|
|
then Just $ SaplingSpendingKey res
|
2024-03-10 12:47:26 +00:00
|
|
|
else Nothing
|
|
|
|
where
|
2024-03-11 20:23:29 +00:00
|
|
|
res =
|
|
|
|
withPureBorshVarBuffer
|
2024-03-13 17:50:39 +00:00
|
|
|
(rustWrapperSaplingSpendingkey
|
2024-03-14 16:30:54 +00:00
|
|
|
(getBytes seed)
|
2024-03-13 17:50:39 +00:00
|
|
|
(fromIntegral $ getValue c)
|
|
|
|
(fromIntegral i))
|
2024-03-10 12:47:26 +00:00
|
|
|
|
2024-03-10 15:01:19 +00:00
|
|
|
-- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey and a Diversifier Index
|
2024-03-12 21:03:35 +00:00
|
|
|
genSaplingPaymentAddress :: Int -> SaplingSpendingKey -> Maybe SaplingReceiver
|
|
|
|
genSaplingPaymentAddress i extspk =
|
2024-03-10 12:47:26 +00:00
|
|
|
if BS.length res == 43
|
2024-03-14 16:30:54 +00:00
|
|
|
then Just $ SaplingReceiver res
|
2024-03-10 12:47:26 +00:00
|
|
|
else Nothing
|
|
|
|
where
|
|
|
|
res =
|
|
|
|
withPureBorshVarBuffer
|
2024-03-14 16:30:54 +00:00
|
|
|
(rustWrapperSaplingPaymentAddress
|
|
|
|
(getBytes extspk)
|
|
|
|
(fromIntegral (i * 111)))
|
2024-03-10 15:20:10 +00:00
|
|
|
|
|
|
|
-- | Generate an internal Sapling address
|
2024-03-14 17:35:13 +00:00
|
|
|
genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingReceiver
|
2024-03-15 15:11:27 +00:00
|
|
|
genSaplingInternalAddress sk =
|
2024-03-20 16:15:30 +00:00
|
|
|
if BS.length res == 43
|
2024-03-15 15:11:27 +00:00
|
|
|
then Just $ SaplingReceiver res
|
2024-03-12 19:57:43 +00:00
|
|
|
else Nothing
|
|
|
|
where
|
2024-03-15 15:11:27 +00:00
|
|
|
res =
|
|
|
|
withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk)
|
2024-04-10 12:06:04 +00:00
|
|
|
|
|
|
|
-- | Update a Sapling commitment tree
|
|
|
|
updateSaplingCommitmentTree ::
|
2024-04-11 21:01:29 +00:00
|
|
|
SaplingCommitmentTree -- ^ the base tree
|
2024-04-10 12:06:04 +00:00
|
|
|
-> HexString -- ^ the new note commitment
|
2024-04-11 21:01:29 +00:00
|
|
|
-> Maybe SaplingCommitmentTree
|
2024-04-10 12:06:04 +00:00
|
|
|
updateSaplingCommitmentTree tree cmu =
|
2024-04-11 21:01:29 +00:00
|
|
|
if BS.length (hexBytes updatedTree) > 1
|
|
|
|
then Just $ SaplingCommitmentTree updatedTree
|
2024-04-10 12:06:04 +00:00
|
|
|
else Nothing
|
|
|
|
where
|
|
|
|
updatedTree =
|
|
|
|
withPureBorshVarBuffer $
|
2024-04-11 21:01:29 +00:00
|
|
|
rustWrapperReadSaplingCommitmentTree
|
|
|
|
(hexBytes $ sapTree tree)
|
|
|
|
(hexBytes cmu)
|
|
|
|
|
|
|
|
-- | Get the Sapling incremental witness from a commitment tree
|
|
|
|
getSaplingWitness :: SaplingCommitmentTree -> Maybe SaplingWitness
|
|
|
|
getSaplingWitness tree =
|
|
|
|
if BS.length (hexBytes wit) > 1
|
|
|
|
then Just $ SaplingWitness wit
|
|
|
|
else Nothing
|
|
|
|
where
|
|
|
|
wit =
|
|
|
|
withPureBorshVarBuffer $
|
|
|
|
rustWrapperReadSaplingWitness (hexBytes $ sapTree tree)
|
|
|
|
|
|
|
|
-- | Get the Sapling note position from a witness
|
|
|
|
getSaplingNotePosition :: SaplingWitness -> Integer
|
|
|
|
getSaplingNotePosition =
|
|
|
|
fromIntegral . rustWrapperReadSaplingPosition . hexBytes . sapWit
|
2024-04-16 18:39:56 +00:00
|
|
|
|
2024-04-26 00:56:29 +00:00
|
|
|
updateSaplingWitness :: SaplingWitness -> [HexString] -> SaplingWitness
|
|
|
|
updateSaplingWitness wit cmus =
|
|
|
|
SaplingWitness $
|
|
|
|
withPureBorshVarBuffer $
|
|
|
|
rustWrapperUpdateSaplingWitness (toBytes $ sapWit wit) (map toBytes cmus)
|
|
|
|
|
2024-04-16 22:51:14 +00:00
|
|
|
-- | Encode a SaplingReceiver into HRF text
|
|
|
|
encodeSaplingAddress :: ZcashNet -> SaplingReceiver -> Maybe T.Text
|
2024-04-26 00:56:29 +00:00
|
|
|
encodeSaplingAddress net sr = do
|
2024-04-16 22:51:14 +00:00
|
|
|
case net of
|
2024-04-26 00:56:29 +00:00
|
|
|
MainNet -> Just $ encodeBech32 (C.pack sapPaymentAddressHrp) (getBytes sr)
|
2024-04-16 22:51:14 +00:00
|
|
|
TestNet ->
|
|
|
|
Just $ encodeBech32 (C.pack sapTestPaymentAddressHrp) (getBytes sr)
|
|
|
|
|
|
|
|
-- | Helper to get de Nework Id from FFI response
|
2024-04-26 00:56:29 +00:00
|
|
|
getNetId :: [Word8] -> ZcashNet
|
|
|
|
getNetId [x] = do
|
|
|
|
case x of
|
2024-04-16 18:39:56 +00:00
|
|
|
1 -> MainNet
|
2024-04-26 00:56:29 +00:00
|
|
|
2 -> TestNet
|
2024-04-16 18:39:56 +00:00
|
|
|
|
|
|
|
-- | decode a Sapling address
|
|
|
|
decodeSaplingAddress :: BS.ByteString -> Maybe SaplingAddress
|
2024-04-26 00:56:29 +00:00
|
|
|
decodeSaplingAddress sapling_address = do
|
2024-04-16 18:39:56 +00:00
|
|
|
if BS.length sa > 1
|
|
|
|
then do
|
|
|
|
let sa0 = BS.unpack sa
|
2024-04-26 00:56:29 +00:00
|
|
|
Just $
|
|
|
|
SaplingAddress (getNetId (take 1 sa0)) $
|
|
|
|
SaplingReceiver (BS.pack (drop 1 sa0))
|
2024-04-16 18:39:56 +00:00
|
|
|
else Nothing
|
2024-04-26 00:56:29 +00:00
|
|
|
where
|
|
|
|
sa =
|
|
|
|
withPureBorshVarBuffer $ rustWrapperDecodeSaplingAddress sapling_address
|