Add Sapling commitment tree functionality

This commit is contained in:
Rene Vergara 2024-04-11 16:01:29 -05:00
parent ce6a13e333
commit 0f19e376dc
Signed by: pitmutt
GPG key ID: 65122AD495A7F5B2
7 changed files with 139 additions and 25 deletions

View file

@ -5,8 +5,15 @@ 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 - Functions for Bech32 encoding
- Function to encode a Sapling address - Function to encode a Sapling address

View file

@ -24,14 +24,19 @@ 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::{ merkle_tree::{
read_commitment_tree, read_commitment_tree,
write_commitment_tree write_commitment_tree,
read_incremental_witness,
write_incremental_witness
}, },
zip32::{ zip32::{
Scope as SaplingScope, Scope as SaplingScope,
@ -1146,7 +1151,23 @@ pub extern "C" fn rust_wrapper_derive_orchard_receiver(
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_read_commitment_tree( 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: *const u8,
tree_len: usize, tree_len: usize,
node: *const u8, node: *const u8,
@ -1176,18 +1197,46 @@ pub extern "C" fn rust_wrapper_read_commitment_tree(
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_bech32_encode( pub extern "C" fn rust_wrapper_read_sapling_witness(
hr: *const u8, tree: *const u8,
hr_len: usize, tree_len: usize,
b: *const u8,
b_len: usize,
out: *mut u8, out: *mut u8,
out_len: &mut usize out_len: &mut usize
){ ){
let hr: String = marshall_from_haskell_var(hr, hr_len, RW); let tree_in: Vec<u8> = marshall_from_haskell_var(tree, tree_len, RW);
let hrp = Hrp::parse(&hr).unwrap(); let tree_reader = Cursor::new(tree_in);
let b: Vec<u8> = marshall_from_haskell_var(b, b_len, RW); let ct: CommitmentTree<Node, SAPLING_DEPTH> = read_commitment_tree(tree_reader).unwrap();
let string = bech32::encode::<Bech32>(hrp, &b).unwrap(); let inc_wit = IncrementalWitness::from_tree(ct);
marshall_to_haskell_var(&string, out, out_len, RW); 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
@ -201,14 +202,27 @@ import ZcashHaskell.Types
-> `()' -> `()'
#} #}
{# fun unsafe rust_wrapper_read_commitment_tree as rustWrapperReadSaplingCommitmentTree {# fun unsafe rust_wrapper_read_sapling_commitment_tree as rustWrapperReadSaplingCommitmentTree
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, toBorshVar* `BS.ByteString'& , toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer (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 {# fun unsafe rust_wrapper_bech32_encode as rustWrapperBech32Encode
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, toBorshVar* `BS.ByteString'& , toBorshVar* `BS.ByteString'&
@ -216,3 +230,4 @@ import ZcashHaskell.Types
} }
-> `()' -> `()'
#} #}

View file

@ -20,6 +20,8 @@ module ZcashHaskell.Sapling where
import C.Zcash import C.Zcash
( rustWrapperIsShielded ( rustWrapperIsShielded
, rustWrapperReadSaplingCommitmentTree , rustWrapperReadSaplingCommitmentTree
, rustWrapperReadSaplingPosition
, rustWrapperReadSaplingWitness
, rustWrapperSaplingCheck , rustWrapperSaplingCheck
, rustWrapperSaplingChgPaymentAddress , rustWrapperSaplingChgPaymentAddress
, rustWrapperSaplingDecodeEsk , rustWrapperSaplingDecodeEsk
@ -43,8 +45,10 @@ import ZcashHaskell.Types
, DecodedNote(..) , DecodedNote(..)
, RawData(..) , RawData(..)
, RawTxResponse(..) , RawTxResponse(..)
, SaplingCommitmentTree(..)
, SaplingReceiver(..) , SaplingReceiver(..)
, SaplingSpendingKey(..) , SaplingSpendingKey(..)
, SaplingWitness(..)
, Scope(..) , Scope(..)
, Seed(..) , Seed(..)
, ShieldedOutput(..) , ShieldedOutput(..)
@ -170,14 +174,32 @@ genSaplingInternalAddress sk =
-- | Update a Sapling commitment tree -- | Update a Sapling commitment tree
updateSaplingCommitmentTree :: updateSaplingCommitmentTree ::
HexString -- ^ the base tree SaplingCommitmentTree -- ^ the base tree
-> HexString -- ^ the new note commitment -> HexString -- ^ the new note commitment
-> Maybe HexString -> Maybe SaplingCommitmentTree
updateSaplingCommitmentTree tree cmu = updateSaplingCommitmentTree tree cmu =
if BS.length updatedTree > 1 if BS.length (hexBytes updatedTree) > 1
then Just $ HexString updatedTree then Just $ SaplingCommitmentTree updatedTree
else Nothing else Nothing
where where
updatedTree = updatedTree =
withPureBorshVarBuffer $ withPureBorshVarBuffer $
rustWrapperReadSaplingCommitmentTree (hexBytes tree) (hexBytes cmu) 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

@ -50,6 +50,8 @@ import ZcashHaskell.Sapling
, genSaplingInternalAddress , genSaplingInternalAddress
, genSaplingPaymentAddress , genSaplingPaymentAddress
, genSaplingSpendingKey , genSaplingSpendingKey
, getSaplingNotePosition
, getSaplingWitness
, getShieldedOutputs , getShieldedOutputs
, isValidSaplingViewingKey , isValidSaplingViewingKey
, isValidShieldedAddress , isValidShieldedAddress
@ -72,6 +74,7 @@ import ZcashHaskell.Types
, RawTxOut(..) , RawTxOut(..)
, RawTxResponse(..) , RawTxResponse(..)
, RawZebraTx(..) , RawZebraTx(..)
, SaplingCommitmentTree(..)
, SaplingReceiver(..) , SaplingReceiver(..)
, SaplingSpendingKey(..) , SaplingSpendingKey(..)
, Scope(..) , Scope(..)
@ -843,10 +846,10 @@ 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 describe "Sapling commitment trees" $ do
let tree = let tree =
SaplingCommitmentTree $
hexString hexString
"01916df07670600aefa3b412a120d6b8d9a3d2ff9466a7ec770cd52d34ddb42313001000013c60b031a5e44650059fcc7101a3f551b807ab8b3a116a5a9c7fa0f3babbe735017c0d36686294ff19d59e58b6a2ac6a7ad607a804bc202c84012d8e94f233970c0128dbde5180af5304d8577376d78297130b615a327974c10881f6d876869aea05011b80b4ca60f74dfe33c78b062df73c84b8b44dab4604db16f5b61eea40134373010c96e4cc8a6a80fba0d41e4eb3070d80769104dc33fb61133b1304c15bf9e23e000107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" "01916df07670600aefa3b412a120d6b8d9a3d2ff9466a7ec770cd52d34ddb42313001000013c60b031a5e44650059fcc7101a3f551b807ab8b3a116a5a9c7fa0f3babbe735017c0d36686294ff19d59e58b6a2ac6a7ad607a804bc202c84012d8e94f233970c0128dbde5180af5304d8577376d78297130b615a327974c10881f6d876869aea05011b80b4ca60f74dfe33c78b062df73c84b8b44dab4604db16f5b61eea40134373010c96e4cc8a6a80fba0d41e4eb3070d80769104dc33fb61133b1304c15bf9e23e000107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
let cmu1 = let cmu1 =
@ -859,10 +862,18 @@ main = do
hexString hexString
"01a47029e9b43722c57143a5d07681bff3e2315c9a28ad49d69e7c1f2f6e81ac160010000000000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" "01a47029e9b43722c57143a5d07681bff3e2315c9a28ad49d69e7c1f2f6e81ac160010000000000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
it "Commitment tree is updated correctly" $ do 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 let t1 = updateSaplingCommitmentTree tree cmu1
case t1 of case t1 of
Nothing -> assertFailure "Tree 1 failed" Nothing -> assertFailure "Failed to append node to tree"
Just t2 -> updateSaplingCommitmentTree t2 cmu2 `shouldBe` Just tree2 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 describe "Extract Sapling Address - UA Valid" $ do
let sr = let sr =
getSaplingFromUA getSaplingFromUA

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