Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add DLEQ proof based on BIP374 and use it for silent payments #1651

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

stratospher
Copy link
Contributor

built on top of f42e0dd.

BIP352 requires senders to compute output scripts using ECDH shared secrets from the same secret keys used to sign the inputs. Generating an incorrect signature will produce an invalid transaction that will be rejected by consensus. An incorrectly generated output script can still be consensus-valid, meaning funds may be lost if it gets broadcast. By producing a DLEQ proof for the generated ECDH shared secrets, the signing entity can prove to other entities that the output scripts have been generated correctly without revealing the private keys.

(from BIP 374)

This PR:

  • adds support for DLEQ proof generation and verification using secp256k1_dleq_prove, secp256k1_dleq_verify based on implementation in secp256k1-zkp
  • introduces a new structure for storing proofs in silent payment module - secp256k1_silentpayments_dleq_data which stores the shared secret computed using ECDH, DLEQ proof and also the index of which recipient in the original unsorted recipient array the proof refers to.
  • adds 4 new APIs in silent payments module to create, verify, serialize and parse proof
    1. sender can create proof using secp256k1_silentpayments_sender_create_outputs_with_proof
      • in the existing secp256k1_silentpayments_sender_create_outputs API, output pubkey is created by iterating over all the recipients and computing ECDH for each unique recipient.
      • since creating the proof also requites iterating over all recipients and computing ECDH for each unique recipient, compute both proof(s) and output pubkey(s) in a new function - secp256k1_silentpayments_sender_create_outputs_with_proof.
      • secp256k1_silentpayments_sender_create_outputs can use secp256k1_silentpayments_sender_create_outputs_with_proofinternally.
    2. both sender and receiver can verify proof using secp256k1_silentpayments_verify_proof
    3. Serialisation function (secp256k1_silentpayments_dleq_data_serialize) serialises secp256k1_silentpayments_dleq_data structure into bytes. secp256k1_silentpayments_dleq_data_parse parses the bytes back into secp256k1_silentpayments_dleq_data structure.
      1. useful if proof creation is done on 1 sender side device (ex: hardware wallet) and proof verification is done on another sender side device (ex: software wallet). secp256k1_silentpayments_dleq_data structure can be serialised into bytes using secp256k1_silentpayments_dleq_data_serialize and then the bytes can be sent from 1 device to another.
      2. secp256k1_silentpayments_dleq_data_parse can reconstruct the bytes back into secp256k1_silentpayments_dleq_data structure which can then be used for proof verification.

open questions:

  1. is the API design ok?
    1. see examples/silentpayments.c for 2 scenarios in which proof verification could be useful. (Alice sends bitcoins to Bob and Carol in 1 silent payment transaction)
      1. useful situation - On Alice’s side
        1. Alice’s hardware wallet creates proof
        2. Alice’s hardware wallet sends proof to Alice’s software wallet
        3. Alice’s software wallet verifies proof and makes sure ECDH shared secrets for generating SP output is computed correctly. Alice can get additional safety guarantee about computed SP output before sending funds.
      2. not sure if this situation is useful - On Bob’s/Carol’s side
        1. Alice’s software wallet sends proof to Bob’s/Carol’s software wallet
        2. Bob’s/Carol’s software wallet verifies proof and makes sure ECDH shared secrets for generating SP output is computed correctly.
  2. should this be shipped with SP module or separately? only problem with separate release would be redundant APIs like secp256k1_silentpayments_sender_create_outputs and secp256k1_silentpayments_sender_create_outputs_with_proof for backward compatibility.

Huge thanks to @ theStack for brainstorming and discussing this with me! ❤️

theStack and others added 9 commits February 3, 2025 21:14
Add a routine for the entire sending flow which takes a set of private keys,
the smallest outpoint, and list of recipients and returns a list of
x-only public keys by performing the following steps:

1. Sum up the private keys
2. Calculate the input_hash
3. For each recipient group:
    3a. Calculate a shared secret
    3b. Create the requested number of outputs

This function assumes a single sender context in that it requires the
sender to have access to all of the private keys. In the future, this
API may be expanded to allow for a multiple senders or for a single
sender who does not have access to all private keys at any given time,
but for now these modes are considered out of scope / unsafe.

Internal to the library, add:

1. A function for creating shared secrets (i.e., a*B or b*A)
2. A function for generating the "SharedSecret" tagged hash
3. A function for creating a single output public key

Finally, add tests for the sender API.
Add function for creating a label tweak. This requires a tagged hash
function for labels. This function is used by the receiver for creating
labels to be used for a) creating labelled addresses and b) to populate
a labels cache when scanning.

Add function for creating a labelled spend pubkey. This involves taking
a label tweak, turning it into a public key and adding it to the spend
public key. This function is used by the receiver to create a labelled
silent payment address.

Add tests for the label API.
Add routine for scanning a transaction and returning the necessary
spending data for any found outputs. This function works with labels via
a lookup callback and requires access to the transaction outputs.
Requiring access to the transaction outputs is not suitable for light
clients, but light client support is enabled by exposing the
`_create_shared_secret` and `_create_output_pubkey` functions in the
API. This means the light client will need to manage their own scanning
state, so wherever possible it is preferrable to use the
`_recipient_scan_ouputs` function.

Add an opaque data type for passing around the summed input public key (A_sum)
and the input hash tweak (input_hash). This data is passed to the scanner
before the ECDH step as two separate elements so that the scanner can
multiply b_scan * input_hash before doing ECDH.

Add functions for deserializing / serializing a public_data object to
and from a public key. When serializing a public_data object, the
input_hash is multplied into A_sum. This is so the object can be stored
as public key for wallet rescanning later, or to vend to light clients.
For the light client, a `_parse` function is added which parses the
compressed public key serialization into a `public_data` object.

Finally, add test coverage for the recieiving API.
Demonstrate sending, scanning, and light client scanning.
Add a benchmark for a full transaction scan and for scanning a single
output. Only benchmarks for scanning are added as this is the most
performance critical portion of the protocol.
Add the BIP-352 test vectors. The vectors are generated with a Python script
that converts the .json file from the BIP to C code:

$ ./tools/tests_silentpayments_generate.py test_vectors.json > ./src/modules/silentpayments/vectors.h
Copy link
Contributor

@theStack theStack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK, nice! Left some initial comments for the BIP374-only parts (first two commits).

src/modules/silentpayments/tests_impl.h Outdated Show resolved Hide resolved
src/modules/silentpayments/tests_impl.h Outdated Show resolved Hide resolved
Comment on lines +58 to +57
if (!secp256k1_eckey_pubkey_serialize(p, buf, &size, 1)) {
return 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pubkey serialization should always succeed here (as it would only fail if p is point at inifinity), so could VERIFY_CHECK that instead, like e.g.

ret = secp256k1_eckey_pubkey_serialize(pk, buf, &buflen, 1);
#ifdef VERIFY
/* Serialization does not fail since the pk is not the point at infinity
* (according to this function's precondition). */
VERIFY_CHECK(ret && buflen == sizeof(buf));
#else
(void) ret;
#endif

Copy link
Contributor Author

@stratospher stratospher Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm true. but since input to this function is secp256k1_ge and not secp256k1_pubkey, unsure if it makes sense to restrict possible values of secp256k1_ge here? (in the musig example, input to the function is secp256k1_pubkey)

src/modules/silentpayments/dleq_impl.h Outdated Show resolved Hide resolved
src/modules/silentpayments/dleq_impl.h Outdated Show resolved Hide resolved
src/modules/silentpayments/dleq_impl.h Outdated Show resolved Hide resolved
src/modules/silentpayments/dleq_impl.h Outdated Show resolved Hide resolved
src/modules/silentpayments/tests_impl.h Outdated Show resolved Hide resolved
- modify secp256k1-zkp's dleq implementation to be consistent with
  BIP 374.
- use BIP374 notations.
- add DLEQ tests
Add BIP374 test vectors. The vectors are generated with a Python script
that converts the 2 csv files from the BIP - test_vectors_generate_proof.csv
and test_vectors_verify_proof.csv to C code:

$ ./tools/test_vectors_dleq_generate.py bips/bip-0374 > ./src/modules/silentpayments/dleq_vectors.h
- add new internal function which returns both DLEQ proof and
  shared secret.
- the existing secp256k1_silentpayments_create_shared_secret API
is refactored to use
secp256k1_silentpayments_create_shared_secret_with_proof.
- structure contains 33-byte shared secret point + 64-byte DLEQ proof
  + index of recipient in original unsorted array of silent payment
    recipients
- add functions to serialise and parse the structure to/from bytes
- add new API which returns DLEQ proofs along with outputs for recipients
- the existing API secp256k1_silentpayments_sender_create_outputs simply
  calls secp256k1_silentpayments_sender_create_outputs_with_proof
  internally.
- in examples/silentpayments.c
    - sender now generates outputs with proof and verifies the proof
    - proof can be serialised to bytes and sent to recipient
    - bytes can be parsed back to proof by recipient as well
    - recipient can verify proof
- in tests_recipients_helper, run_silentpayments_test_vector_send
    - along with output checks, generate DLEQ proofs and verify them

add detailed examples showing parsing/serialisation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants