[WIP] Add bip352 silentpayments module#721
[WIP] Add bip352 silentpayments module#721jlest01 wants to merge 7 commits intorust-bitcoin:masterfrom
Conversation
This modification allows running the command `./vendor-libsecp.sh -f pull/1519/head` to obtain MuSig2 components from `bitcoin-core/secp256k1`. This commit may be discarded after merging PR #1519.
ec34fda to
c217147
Compare
src/constants.rs
Outdated
| /// The size of a full ElligatorSwift encoding. | ||
| pub const ELLSWIFT_ENCODING_SIZE: usize = 64; | ||
|
|
||
| /// The size of a silent payment public data. |
There was a problem hiding this comment.
in 0e7e062 ("Add Silent Payments module"):
nit: "silent payments"
| /// The size of a silent payment public data. | |
| /// The size of a silent payments public data object. |
There was a problem hiding this comment.
Thanks for the review. Done !
| pub fn silentpayments_sender_create_outputs<C: Verification>( | ||
| secp: &Secp256k1<C>, | ||
| recipients: &[SilentpaymentsRecipient], | ||
| smallest_outpoint: &[u8; 36], |
There was a problem hiding this comment.
in 0e7e062 ("lib: Add Silent Payments module"):
Would it make sense to use an Outpoint type here (assuming one exists)? On the one hand, it makes things more clear and less foot gunny for the caller, on the other hand I can see an argument for having the rust-secp256k1 crate be unaware of Bitcoin protocol specifics.
There was a problem hiding this comment.
Sounds like we should wait for bitcoin-primitives to be released and gate this behind an optional dependency on it.
There was a problem hiding this comment.
I had a massive brainfart here. We want to optionally use crypto in primitives which will optionally depend on secp256k1, so secp256k1 cannot (optionally) depend on primitives as it'd create a cycle if all features are on. I'm not sure what the best solution is but having silent payments implemented outside secp256k1 sounds most appealing at first thought.
src/silentpayments.rs
Outdated
| /// Creates an `SilentpaymentsPublicData` object from a 98-byte array. | ||
| pub fn from_array(data: [u8; 98]) -> SilentpaymentsPublicData { | ||
| SilentpaymentsPublicData(ffi::SilentpaymentsPublicData::from_array(data)) | ||
| } | ||
|
|
||
| /// Returns the 64-byte array representation of this `SilentpaymentsPublicData` object. | ||
| pub fn to_array(&self) -> [u8; 98] { self.0.to_array() } |
There was a problem hiding this comment.
in 0e7e062 ("lib: Add Silent Payments module"):
How do you foresee the to_array/from_array functions being used? public_data is meant to be an opaque data type, so it seems better to me to not have these methods and instead have callers always use the create and serialize functions, but perhaps I'm missing something.
c217147 to
36b2c6b
Compare
|
Removed dynamically sized vector in most functions and simplified the code.
|
| unsafe { | ||
| // Get a pointer to the inner ffi::PublicKey | ||
| let ffi_pubkey_ptr: *const ffi::PublicKey = pubkey.as_c_ptr(); | ||
|
|
||
| // Dereference the pointer to get the ffi::PublicKey | ||
| // Then create a copy of it | ||
| (*ffi_pubkey_ptr).clone() | ||
| } |
There was a problem hiding this comment.
Unnecessary unsafe and clone, PublicKey wraps ffi::PublicKey and they are both Copy.
src/silentpayments.rs
Outdated
| for (i, pubkey) in out_pubkeys.iter_mut().enumerate() { | ||
| out_pubkeys_ptrs[i] = pubkey as *mut _; | ||
| } |
There was a problem hiding this comment.
Looks redundant, doesn't out_pubkeys.iter_mut().map(|k| k as *mut _).collect() (line 81) do the same?
|
|
||
| let (ffi_taproot_seckeys, n_taproot_seckeys) = match taproot_seckeys { | ||
| Some(keys) => { | ||
| let ffi_keys: &[*const ffi::Keypair] = transmute::<&[&Keypair], &[*const ffi::Keypair]>(taproot_seckeys.unwrap()); |
There was a problem hiding this comment.
Pointer casting is recommended over transmuting pointers and references, but both approaches are unsound if Keypair is not guaranteed to have the same layout as ffi::Keypair. Adding #[repr(transparent)] to Keypair fixes this.
Same comment for SecretKey a few lines down, and some more in this file, please check all uses of transmute and use pointer casting where possible, and review if the source and destination type are layout-compatible.
src/silentpayments.rs
Outdated
| ); | ||
|
|
||
| if res == 1 { | ||
| Ok(out_pubkeys.into_iter().map(XOnlyPublicKey::from).collect()) |
There was a problem hiding this comment.
This can be optimized if XOnlyPublicKey is #[repr(transparent)]. (using Vec::from_raw_parts)
src/silentpayments.rs
Outdated
| return Ok(LabelTweakResult { pubkey, label_tweak }); | ||
| } else { | ||
| return Err(LabelTweakError::Failure); | ||
| } |
There was a problem hiding this comment.
Syntax could be more "rusty" by removing the 'return' keywords (this is a clippy warning, see other clippy warnings for more simple improvements like this)
src/silentpayments.rs
Outdated
| unsafe { | ||
|
|
||
| let empty_data = [0u8; constants::SILENT_PAYMENTS_PUBLIC_DATA_SIZE]; | ||
| let mut silentpayments_public_data = ffi::SilentpaymentsPublicData::from_array(empty_data); |
There was a problem hiding this comment.
This can use Self::new(), and as_mut_c_ptr() for the c function. It then does not need to be wrapped before returning.
Same comment for line 308.
src/silentpayments.rs
Outdated
| let mut result = vec![SilentpaymentsFoundOutput::empty(); n_found_outputs]; | ||
|
|
||
| for i in 0..n_found_outputs { | ||
| result[i] = SilentpaymentsFoundOutput(out_found_output[i]); | ||
|
|
||
| } |
There was a problem hiding this comment.
Can be optimized to use Vec::from_raw_parts if SilentpaymentsFoundOutput is made #[repr(transparent)].
| label_lookup: SilentpaymentsLabelLookupFunction, | ||
| label_context: L, |
There was a problem hiding this comment.
This requires users to use unsafe, and is maybe even unsound to accept a function that can return pointers to a "safe" (not annotated unsafe) function.
There was a problem hiding this comment.
This is also unsound on Rust < 1.81, see https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#abort-on-uncaught-panics-in-extern-c-functions. To fix that you have to catch_unwind and abort manually when using older versions.
There was a problem hiding this comment.
I think I managed to create a safe abstraction for this callback (given the current restrictions, which might change as bitcoin-core/secp256k1#1519 is not merged yet). I added it (2b3f633) to a branch on my fork, feel free to add (may be modified) it here.
There was a problem hiding this comment.
Thanks @antonilol for all your contributions, here and to other projects like electrs! They are very helpful.
There was a problem hiding this comment.
Thanks for working on this and proposing a better approach.
But I haven't added the commit yet because it still has some TODO and the example file is not using the modified function.
I would like to better understand your proposal and how we can implement the TODOs and call the function from the example file.
There was a problem hiding this comment.
First TODO: the C library allows you to pass a null pointer for the label lookup function, but the function signature on the Rust side does not, this type definition must be wrapped in Option<...> to allow null pointers.
Second TODO: the C library checks that the context pointer is non null when the function pointer is non null, in my opinion the C library should treat it as opaque and 100% managed by the user so should not check it, just pass it to the callback (comment thread, same link as in the code). Note this will not be an issue for the Rust side anymore when TODO 1 is fixed, but at first I thought that could be harder because bindings are normally generated.
Function pointer + context pointer is a C thing, in Rust a closure automatically captures the variables it needs. To allow users to pass closures, the closure needs to be "converted" to a function pointer and context pointer for the C side to use.
This safe wrapper stores the state part of the closure (a Rust closure can be seen as a combination of a function and state) on the stack and passes the pointer to that as the context pointer. In the callback the function and the state are but "back together" and called.
To add to this, the C side expects a pointer to the return value. The C function uses this after the callback returned (obviously), so this pointer needs to live for longer than the callback, but as long as the safe wrapper is enough, so that return value is stored on the stack as well. This storage (the variable named storage) is passed with the closure state to the C side as the context pointer.
Calling from the example (or from any user code) should be straightforward, these should (roughly) be the changes needed to the example:
- make the cache a Vec for simplicity
- change the callback function (in the argument list) to
|label| cache.iter().find(|cache_entry| cache_entry.label == label).map(|cache_entry| cache_entry.label_tweak)cache will be a captured variable and the safe wrapper will take care of getting it to the C side.
There was a problem hiding this comment.
FnMut(&[u8; 33]) -> Option<[u8; 32]> should be the right bound for the label_lookup parameter, I explored an alternative here.
There was a problem hiding this comment.
Hi @antonilol, I've applied your suggestion in the bip352/bindings_take_4 branch tracking the silent payments libsecp256k1 take 4 PR. I'm still getting my head around it, but is great how it simplifies the API and avoids the implementation of unsafe code from part of the user, thanks!
| } | ||
| } | ||
|
|
||
| impl SilentpaymentsFoundOutput { |
There was a problem hiding this comment.
Maybe we should implement some getter functions too?
There was a problem hiding this comment.
What would be the use cases for the getter functions?
There was a problem hiding this comment.
I may be missing something, but in the tests we're just printing the SilentpaymentsFoundOutput returned by silentpayments_recipient_scan_outputs, and I can't figure out how I would take the x only pubkey to update my wallet state, let alone use the tweak to actually create the spending key for that output.
There was a problem hiding this comment.
I made a quick commit just to show you what I mean https://github.com/Sosthene00/rust-secp256k1/tree/bip352-add-getters
36b2c6b to
9566ad2
Compare
|
Hey @jlest01, I made a really gross attempt to get a silent payment scanned through the current bindings and reached a boundary error at the end, while calling |
|
Hi @nymius, could you explain the error better and how to reproduce it ? |
| impl_array_newtype!(SilentpaymentsPublicData, u8, 98); | ||
| impl_raw_debug!(SilentpaymentsPublicData); | ||
|
|
||
| pub type SilentpaymentsLabelLookupFunction = unsafe extern "C" fn(*const c_uchar, *const c_void) -> *const c_uchar; |
There was a problem hiding this comment.
| pub type SilentpaymentsLabelLookupFunction = unsafe extern "C" fn(*const c_uchar, *const c_void) -> *const c_uchar; | |
| pub type SilentpaymentsLabelLookupFunction = Option<unsafe extern "C" fn(*const c_uchar, *const c_void) -> *const c_uchar>; |
This should allow calling secp256k1_silentpayments_recipient_scan_outputs with a NULL label_lookup.
Uses in this pull request will have to be updated by wrapping the function pointer in Some(...)
|
Hello @jlest01 , I've been working on top of these changes to update to the most recent state of the silent payments module proposal for libsecp256k1. Then I tried to remove some of the large flat prefixes that predate the C code with a modular approach taking advantage of the Rust module system and feature gating the silent payment full scanning implementation and the light scanning one, to make its compilation optional. These changes are in the bip352/bindings_take_3 branch (notice the previous branch is different as this one doesn't have the Finally, after the silent payments PR take 3 got closed in favor of the new bitcoin-core/secp256k1#1765 silent payments (take 4) PR. I pulled the branch to my own fork to start maintaining it on top of One thing to highlight, is that the In all these branches I have a large commit count, but I'll squash them if we agree on the viability of this idea. Are you still interested in working on this? What do you think about closing this PR in favor of opening a new one with the changes in bip352/bindings_take_4? |
|
Hello @nymius , thanks for updating the silent payments module! |
|
Closing this in favor of #876 |
This PR adds bip352
silentpaymentsmodule based on bitcoin-core/secp256k1#1519.Currently, it creates bindings for all silent payment structs and functions and also adds the high-level API to access them.
examples/silentpayments.rsimplements the same example asexamples/silentpayments.cfrom the original repository and shows how to use all functions.The example can be run with
cargo run --example silentpayments --features="rand std".Although it implements all the features of the
silentpaymentsmodule, this is still a work in progress, as the current module only works with the standard library.For review purposes, only the last three commits matter. The others may be discarded after the module is merged into the original repository and the
dependfolder updated.