Compare commits

...

10 commits

9 changed files with 279 additions and 9 deletions

View file

@ -5,9 +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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.4.1]
### Added
- Functions to handle Sapling commitment trees, incremental witnesses and note positions
## [0.5.4.0] ## [0.5.4.0]
### Added
- Function to decode Orchard actions with a spending key - Function to decode Orchard actions with a spending key
- Functions for Bech32 encoding
- Function to encode a Sapling address
## [0.5.3.0] ## [0.5.3.0]

View file

@ -6,7 +6,8 @@ use std::{
marker::PhantomData, marker::PhantomData,
io::{ io::{
Write, Write,
Cursor Cursor,
Error
}, },
}; };
@ -23,11 +24,20 @@ use haskell_ffi::{
FromHaskell, HaskellSize, ToHaskell FromHaskell, HaskellSize, ToHaskell
}; };
use incrementalmerkletree::frontier::CommitmentTree; use incrementalmerkletree::{
frontier::CommitmentTree,
witness::IncrementalWitness
};
use zip32; use zip32;
use zcash_primitives::{ use zcash_primitives::{
merkle_tree::{
read_commitment_tree,
write_commitment_tree,
read_incremental_witness,
write_incremental_witness
},
zip32::{ zip32::{
Scope as SaplingScope, Scope as SaplingScope,
ChildIndex, ChildIndex,
@ -57,6 +67,7 @@ use zcash_primitives::{
MerklePath, MerklePath,
NOTE_COMMITMENT_TREE_DEPTH as SAPLING_DEPTH, NOTE_COMMITMENT_TREE_DEPTH as SAPLING_DEPTH,
PaymentAddress, PaymentAddress,
note::ExtractedNoteCommitment as SaplingNoteCommitment,
keys::{ keys::{
PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey, PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey,
ExpandedSpendingKey, ExpandedSpendingKey,
@ -105,6 +116,7 @@ use orchard::{
use bech32::{ use bech32::{
Hrp, Hrp,
Bech32,
Bech32m Bech32m
}; };
@ -187,6 +199,14 @@ pub struct Hhex {
bytes: Vec<u8> bytes: Vec<u8>
} }
impl<RW> ToHaskell<RW> for Hhex {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
#[derive(BorshSerialize, BorshDeserialize)] #[derive(BorshSerialize, BorshDeserialize)]
pub struct Haction { pub struct Haction {
nf: Hhex, nf: Hhex,
@ -585,7 +605,7 @@ pub extern "C" fn rust_wrapper_bech32decode(
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_bech32_encode( pub extern "C" fn rust_wrapper_bech32m_encode(
hr: *const u8, hr: *const u8,
hr_len: usize, hr_len: usize,
b: *const u8, b: *const u8,
@ -1129,3 +1149,94 @@ pub extern "C" fn rust_wrapper_derive_orchard_receiver(
marshall_to_haskell_var(&o_rec.to_raw_address_bytes().to_vec(), out, out_len, RW); marshall_to_haskell_var(&o_rec.to_raw_address_bytes().to_vec(), out, out_len, RW);
} }
#[no_mangle]
pub extern "C" fn rust_wrapper_bech32_encode(
hr: *const u8,
hr_len: usize,
b: *const u8,
b_len: usize,
out: *mut u8,
out_len: &mut usize
) {
let hr: String = marshall_from_haskell_var(hr, hr_len, RW);
let hrp = Hrp::parse(&hr).unwrap();
let b: Vec<u8> = marshall_from_haskell_var(b, b_len, RW);
let string = bech32::encode::<Bech32>(hrp, &b).unwrap();
marshall_to_haskell_var(&string, out, out_len, RW);
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_sapling_commitment_tree(
tree: *const u8,
tree_len: usize,
node: *const u8,
node_len: usize,
out: *mut u8,
out_len: &mut usize
){
let tree_in: Vec<u8> = marshall_from_haskell_var(tree, tree_len, RW);
let tree_reader = Cursor::new(tree_in);
let mut ct: CommitmentTree<Node, SAPLING_DEPTH> = read_commitment_tree(tree_reader).unwrap();
let node_in: Vec<u8> = marshall_from_haskell_var(node, node_len, RW);
let n = Node::from_cmu(&SaplingNoteCommitment::from_bytes(&to_array(node_in)).unwrap());
ct.append(n);
let mut out_bytes: Vec<u8> = Vec::new();
let result = write_commitment_tree(&ct, &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);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_sapling_witness(
tree: *const u8,
tree_len: usize,
out: *mut u8,
out_len: &mut usize
){
let tree_in: Vec<u8> = marshall_from_haskell_var(tree, tree_len, RW);
let tree_reader = Cursor::new(tree_in);
let ct: CommitmentTree<Node, SAPLING_DEPTH> = read_commitment_tree(tree_reader).unwrap();
let inc_wit = IncrementalWitness::from_tree(ct);
let mut out_bytes: Vec<u8> = Vec::new();
let result = write_incremental_witness(&inc_wit, &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);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_sapling_position(
wit: *const u8,
wit_len: usize,
) -> u64 {
let wit_in: Vec<u8> = marshall_from_haskell_var(wit, wit_len, RW);
let wit_reader = Cursor::new(wit_in);
let iw: IncrementalWitness<Node, SAPLING_DEPTH> = read_incremental_witness(wit_reader).unwrap();
let path = iw.path();
match path {
Some(p) => {
let pos = p.position();
return u64::from(pos);
},
None => {
return 0;
}
}
}

View file

@ -21,6 +21,7 @@ import qualified Data.Text as T
import Data.Word import Data.Word
import Data.Int import Data.Int
import Data.Structured import Data.Structured
import Data.HexString (HexString(..))
import Foreign.C.Types import Foreign.C.Types
import Foreign.Rust.Marshall.External import Foreign.Rust.Marshall.External
import Foreign.Rust.Marshall.Fixed import Foreign.Rust.Marshall.Fixed
@ -38,7 +39,7 @@ import ZcashHaskell.Types
-> `()' -> `()'
#} #}
{# fun unsafe rust_wrapper_bech32_encode as rustWrapperBech32Encode {# fun unsafe rust_wrapper_bech32m_encode as rustWrapperBech32mEncode
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, toBorshVar* `BS.ByteString'& , toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer (T.Text)'& , getVarBuffer `Buffer (T.Text)'&
@ -200,3 +201,33 @@ import ZcashHaskell.Types
} }
-> `()' -> `()'
#} #}
{# fun unsafe rust_wrapper_read_sapling_commitment_tree as rustWrapperReadSaplingCommitmentTree
{ toBorshVar* `BS.ByteString'&
, toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_read_sapling_witness as rustWrapperReadSaplingWitness
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun pure unsafe rust_wrapper_read_sapling_position as rustWrapperReadSaplingPosition
{ toBorshVar* `BS.ByteString'&
}
-> `Word64'
#}
{# fun unsafe rust_wrapper_bech32_encode as rustWrapperBech32Encode
{ toBorshVar* `BS.ByteString'&
, toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer (T.Text)'&
}
-> `()'
#}

View file

@ -34,7 +34,7 @@ import qualified Data.Text.Encoding as E
import Data.Word import Data.Word
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
import ZcashHaskell.Types import ZcashHaskell.Types
import ZcashHaskell.Utils (encodeBech32m, f4Jumble) import ZcashHaskell.Utils (encodeBech32, encodeBech32m, f4Jumble)
-- | Derives an Orchard spending key for the given seed and account ID -- | Derives an Orchard spending key for the given seed and account ID
genOrchardSpendingKey :: genOrchardSpendingKey ::
@ -155,6 +155,24 @@ decryptOrchardAction key encAction =
withPureBorshVarBuffer $ withPureBorshVarBuffer $
rustWrapperOrchardNoteDecode (o_key key) encAction rustWrapperOrchardNoteDecode (o_key key) encAction
getSaplingFromUA :: BS.ByteString -> Maybe T.Text
getSaplingFromUA uadd = do
let a = isValidUnifiedAddress uadd
case a of
Nothing -> Nothing
Just a -> do
let sraw = s_rec a
case sraw of
Nothing -> Nothing
Just sraw -> do
let net = ua_net a
case net of
MainNet ->
Just $ encodeBech32 (C.pack sapPaymentAddressHrp) (getBytes sraw)
TestNet ->
Just $
encodeBech32 (C.pack sapTestPaymentAddressHrp) (getBytes sraw)
-- | Attemtps to decode the given @OrchardAction@ using the given @OrchardSpendingKey@ -- | Attemtps to decode the given @OrchardAction@ using the given @OrchardSpendingKey@
decryptOrchardActionSK :: decryptOrchardActionSK ::
OrchardSpendingKey -> Scope -> OrchardAction -> Maybe DecodedNote OrchardSpendingKey -> Scope -> OrchardAction -> Maybe DecodedNote

View file

@ -19,6 +19,9 @@ module ZcashHaskell.Sapling where
import C.Zcash import C.Zcash
( rustWrapperIsShielded ( rustWrapperIsShielded
, rustWrapperReadSaplingCommitmentTree
, rustWrapperReadSaplingPosition
, rustWrapperReadSaplingWitness
, rustWrapperSaplingCheck , rustWrapperSaplingCheck
, rustWrapperSaplingChgPaymentAddress , rustWrapperSaplingChgPaymentAddress
, rustWrapperSaplingDecodeEsk , rustWrapperSaplingDecodeEsk
@ -30,7 +33,7 @@ import C.Zcash
) )
import Data.Aeson import Data.Aeson
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.HexString (HexString(..), fromText, toBytes, toText) import Data.HexString (HexString(..), fromText, hexString, toBytes, toText)
import Data.Word import Data.Word
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
( withPureBorshVarBuffer ( withPureBorshVarBuffer
@ -42,8 +45,10 @@ import ZcashHaskell.Types
, DecodedNote(..) , DecodedNote(..)
, RawData(..) , RawData(..)
, RawTxResponse(..) , RawTxResponse(..)
, SaplingCommitmentTree(..)
, SaplingReceiver(..) , SaplingReceiver(..)
, SaplingSpendingKey(..) , SaplingSpendingKey(..)
, SaplingWitness(..)
, Scope(..) , Scope(..)
, Seed(..) , Seed(..)
, ShieldedOutput(..) , ShieldedOutput(..)
@ -166,3 +171,35 @@ genSaplingInternalAddress sk =
where where
res = res =
withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk) 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
else Nothing
where
updatedTree =
withPureBorshVarBuffer $
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

View file

@ -519,6 +519,16 @@ instance FromJSON ShieldedOutput where
p <- obj .: "proof" p <- obj .: "proof"
pure $ ShieldedOutput cv cmu ephKey encText outText p pure $ ShieldedOutput cv cmu ephKey encText outText p
-- | Type for a Sapling note commitment tree
newtype SaplingCommitmentTree = SaplingCommitmentTree
{ sapTree :: HexString
} deriving (Eq, Prelude.Show, Read)
-- | Type for a Sapling incremental witness
newtype SaplingWitness = SaplingWitness
{ sapWit :: HexString
} deriving (Eq, Prelude.Show, Read)
-- * Orchard -- * Orchard
-- | A spending key for Orchard -- | A spending key for Orchard
newtype OrchardSpendingKey = newtype OrchardSpendingKey =

View file

@ -19,6 +19,7 @@ module ZcashHaskell.Utils where
import C.Zcash import C.Zcash
( rustWrapperBech32Decode ( rustWrapperBech32Decode
, rustWrapperBech32mEncode
, rustWrapperBech32Encode , rustWrapperBech32Encode
, rustWrapperF4Jumble , rustWrapperF4Jumble
, rustWrapperF4UnJumble , rustWrapperF4UnJumble
@ -45,7 +46,11 @@ decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode
-- | Encode the given Human Readable Part and bytestring as a Bech32m string -- | Encode the given Human Readable Part and bytestring as a Bech32m string
encodeBech32m :: BS.ByteString -> BS.ByteString -> T.Text encodeBech32m :: BS.ByteString -> BS.ByteString -> T.Text
encodeBech32m h d = withPureBorshVarBuffer $ rustWrapperBech32Encode h d encodeBech32m h d = withPureBorshVarBuffer $ rustWrapperBech32mEncode h d
-- | Encode the given Human Readable Part and bytestring as a Bech32 string
encodeBech32 :: BS.ByteString -> BS.ByteString -> T.Text
encodeBech32 h d = withPureBorshVarBuffer $ rustWrapperBech32Encode h d
-- | Apply the F4Jumble transformation to the given bytestring -- | Apply the F4Jumble transformation to the given bytestring
f4Jumble :: BS.ByteString -> BS.ByteString f4Jumble :: BS.ByteString -> BS.ByteString
@ -115,3 +120,4 @@ readZebraTransaction hex =
else Just rawTx else Just rawTx
where where
rawTx = (withPureBorshVarBuffer . rustWrapperTxRead) $ hexBytes hex rawTx = (withPureBorshVarBuffer . rustWrapperTxRead) $ hexBytes hex

View file

@ -50,10 +50,13 @@ import ZcashHaskell.Sapling
, genSaplingInternalAddress , genSaplingInternalAddress
, genSaplingPaymentAddress , genSaplingPaymentAddress
, genSaplingSpendingKey , genSaplingSpendingKey
, getSaplingNotePosition
, getSaplingWitness
, getShieldedOutputs , getShieldedOutputs
, isValidSaplingViewingKey , isValidSaplingViewingKey
, isValidShieldedAddress , isValidShieldedAddress
, matchSaplingAddress , matchSaplingAddress
, updateSaplingCommitmentTree
) )
import ZcashHaskell.Transparent import ZcashHaskell.Transparent
import ZcashHaskell.Types import ZcashHaskell.Types
@ -71,6 +74,7 @@ import ZcashHaskell.Types
, RawTxOut(..) , RawTxOut(..)
, RawTxResponse(..) , RawTxResponse(..)
, RawZebraTx(..) , RawZebraTx(..)
, SaplingCommitmentTree(..)
, SaplingReceiver(..) , SaplingReceiver(..)
, SaplingSpendingKey(..) , SaplingSpendingKey(..)
, Scope(..) , Scope(..)
@ -842,8 +846,52 @@ main = do
Nothing -> assertFailure "Couldn't decode" Nothing -> assertFailure "Couldn't decode"
Just t' -> do Just t' -> do
let tb = zt_tBundle t' let tb = zt_tBundle t'
print tb
show tb `shouldNotBe` "" 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 "Extract Sapling Address - UA Valid" $ do
let sr =
getSaplingFromUA
"u14a5c4ufn9feqvxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l"
it "Extract sapling address" $ do
case sr of
Nothing ->
assertFailure "UA invalid or does not contain a Sapling receiver"
Just t -> do
print t
t `shouldBe`
"zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60"
describe "Extract Sapling Address - UA Invalid" $ do
let sr =
getSaplingFromUA
"u14a5c4ufn9qfevxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l"
it "Try to extract sapling address from invalid UA" $ do
sr `shouldBe` Nothing
-- | Properties -- | Properties
prop_PhraseLength :: Property prop_PhraseLength :: Property

View file

@ -5,7 +5,7 @@ cabal-version: 3.0
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: zcash-haskell name: zcash-haskell
version: 0.5.4.0 version: 0.5.4.1
synopsis: Utilities to interact with the Zcash blockchain synopsis: Utilities to interact with the Zcash blockchain
description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme> description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme>
category: Blockchain category: Blockchain