Skip to content

Conversation

jvgelder
Copy link

@jvgelder jvgelder commented Oct 8, 2025

Draft for now looking for a bit of feedback on how we can expose the functionality to the developer

Implements bip352 and exposes the following functions:

  • scanForSilentPayments: to find if given outputs belong to our sp address
  • deriveOutput: to create a new silent payment output
  • encodeSilentPaymentAddress
  • decodeSilentPaymentAddress

Note it tests against vectors taken from: https://github.com/bitcoin/bips/blob/master/bip-0352/send_and_receive_test_vectors.json

TODO:

  • Decide how we expose scanning and deriving to the developer i guess via the p2sp object ?
  • Decide whether we return the found hex endcoded or Uint8Array output
  • Figure out of we can remove modN32 and subBE as they might exist?
  • Add derivation logic
  • Replace getPubkeyFromInputTS from test with something that exists?

Do note that when this bitcoin-core/secp256k1#1698 gets merged and we make it available in https://github.com/bitcoinjs/tiny-secp256k1 part of this code becomes redundant but at least we already have the interfaces in place.

Bluewallet also has a TS implementation : https://github.com/BlueWallet/SilentPayments/blob/master/src/index.ts

This PR implements bip352 and exposes the following functions:

- scanForSilentPayments: to find if given outputs belong to our sp address
- deriveOutput: to create a new silent payment output

TODO:
- [] Decide how we expose scanning and deriving to the developer i guess via
the p2sp object ?
- [] Decide whether we return the found hex endcoded or Uint8Array output
- [] Figure out of we can remove `modN32` and `subBE` as they might exist?
- [] Add derivation logic
- [] Replace `getPubkeyFromInputTS` from test with something that exists?
Tags were missing from the TAGS
Some outputs wernt matching what we expected them to be.
@junderw
Copy link
Member

junderw commented Oct 9, 2025

I've been in discussions on the best way to handle this, and I think it would be a better idea to pass in a cached key.

Instead of calculating the sum of the inputs in real time every time, The payment should only take a special key ie. inputAggregate or something with an easier name. Then separate from the Payments API we would offer a helper function that can calculate the aggregate key from given inputs array.

This is primarily because our friends over at BlueWallet see a future where apps like electrs or mempool etc will pre-calculate the aggregate keys and index them for us....

But... yeah... ballooning Payments API into something that encompasses a whole transaction seems like a bit much. At that point we might as well turn it into a method on the Psbt.

@junderw
Copy link
Member

junderw commented Oct 9, 2025

@Overtorment if you'd like to chime in, feel free.

@jvgelder
Copy link
Author

jvgelder commented Oct 10, 2025

I've been in discussions on the best way to handle this, and I think it would be a better idea to pass in a cached key.

Instead of calculating the sum of the inputs in real time every time, The payment should only take a special key ie. inputAggregate or something with an easier name. Then separate from the Payments API we would offer a helper function that can calculate the aggregate key from given inputs array.

This is primarily because our friends over at BlueWallet see a future where apps like electrs or mempool etc will pre-calculate the aggregate keys and index them for us....

But... yeah... ballooning Payments API into something that encompasses a whole transaction seems like a bit much. At that point we might as well turn it into a method on the Psbt.

ye maybe use the following logic:

  • If shared secret S?: Uint8Array and inputTweak?: Uint8Array are set we go straight to deriveOutputs
  • else if inputs?: Input[] and privKeys?: Array<{ priv: Uint8Array; isXOnly: boolean }> we do calculate the tweak and shared secret
  • else we throw an error.
  1. Is there a use case to allow the user to provide A_sum instead of the private keys?
  2. Besides returning outputs we may also want to return a transaction that can be signed or is this not the place to do that?
  3. Similar to the point above do we want to return signed outputs?

@Overtorment
Copy link

ill take a look.

btw we have a TS implementation of SP here: https://github.com/BlueWallet/SilentPayments/

@jvgelder
Copy link
Author

I've been in discussions on the best way to handle this, and I think it would be a better idea to pass in a cached key.
Instead of calculating the sum of the inputs in real time every time, The payment should only take a special key ie. inputAggregate or something with an easier name. Then separate from the Payments API we would offer a helper function that can calculate the aggregate key from given inputs array.
This is primarily because our friends over at BlueWallet see a future where apps like electrs or mempool etc will pre-calculate the aggregate keys and index them for us....
But... yeah... ballooning Payments API into something that encompasses a whole transaction seems like a bit much. At that point we might as well turn it into a method on the Psbt.

ye maybe use the following logic:

* If shared secret `S?: Uint8Array` and `inputTweak?: Uint8Array` are set we go straight to `deriveOutputs`

* else if `inputs?: Input[]` and `privKeys?: Array<{ priv: Uint8Array; isXOnly: boolean }>` we do calculate the tweak and shared secret

* else we throw an error.


1. Is there a use case to allow the user to provide A_sum instead of the private keys?

2. Besides returning outputs we may also want to return a transaction that can be signed or is this not the place to do that?

3. Similar to the point above do we want to return signed outputs?

Allow various cases for deriving of outputs :

lazy.prop(o, 'outputs', () => {

network,
);
});
lazy.prop(o, 'outputs', () => {
Copy link
Author

Choose a reason for hiding this comment

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

This is something I am unsure about.

  1. Should outputs be the ones create to send?
  2. Where and how do we store resulting UTXO when we scanned?

@jvgelder
Copy link
Author

ill take a look.

btw we have a TS implementation of SP here: https://github.com/BlueWallet/SilentPayments/

Ye I mentioned on the top :) (thought I referred to to something inside the core bluewallet app).
Reason for this PR is that I think most of the code belongs in bitcoinjslib (see the use of tagged hash for example).
If you think you have more elegant parts feel free to point out / overwrite some of my code here ( I am not that familiar with typescript)

@junderw
Copy link
Member

junderw commented Oct 10, 2025

ye maybe use the following logic:

  • If shared secret S?: Uint8Array and inputTweak?: Uint8Array are set we go straight to deriveOutputs
  • else if inputs?: Input[] and privKeys?: Array<{ priv: Uint8Array; isXOnly: boolean }> we do calculate the tweak and shared secret
  • else we throw an error.
  1. Is there a use case to allow the user to provide A_sum instead of the private keys?
  2. Besides returning outputs we may also want to return a transaction that can be signed or is this not the place to do that?
  3. Similar to the point above do we want to return signed outputs?

I would like to repeat what I said before:

ballooning Payments API into something that encompasses a whole transaction seems like a bit much. At that point we might as well turn it into a method on the Psbt.

It seems like your reply is ignoring this and talking about placing all of that inside the p2sp Payment API still... am I misunderstanding your response? Could you please clarify?

@jvgelder
Copy link
Author

ye maybe use the following logic:

  • If shared secret S?: Uint8Array and inputTweak?: Uint8Array are set we go straight to deriveOutputs
  • else if inputs?: Input[] and privKeys?: Array<{ priv: Uint8Array; isXOnly: boolean }> we do calculate the tweak and shared secret
  • else we throw an error.
  1. Is there a use case to allow the user to provide A_sum instead of the private keys?
  2. Besides returning outputs we may also want to return a transaction that can be signed or is this not the place to do that?
  3. Similar to the point above do we want to return signed outputs?

I would like to repeat what I said before:

ballooning Payments API into something that encompasses a whole transaction seems like a bit much. At that point we might as well turn it into a method on the Psbt.

It seems like your reply is ignoring this and talking about placing all of that inside the p2sp Payment API still... am I misunderstanding your response? Could you please clarify?

  • scratch point 2 and 3 indeed.

For the inputs/outputs i was thinking of a bare minimal set of data, the parsing of the transactions into receiving inputs would be done elsewhere. I think the lazy output property kinda reflects this now right? or do you still think even that is too much here?

@Overtorment
Copy link

i feel a bit out of element here, every time i get distracted from SP i completely forget how they work and getting familiar again means re-reading all the specs again, and it takes time.

are there any specific questions for me? if yes, dumb it down for me.
otherwise, the standalone lib https://github.com/BlueWallet/SilentPayments/ does the job well, and embeds nicely into whatever js library for bitcoin you are going to use (not necessarily bitcoinjs-lib, more modern js apps choose to use scure-btc-signer instead )

@jvgelder
Copy link
Author

i feel a bit out of element here, every time i get distracted from SP i completely forget how they work and getting familiar again means re-reading all the specs again, and it takes time.

are there any specific questions for me? if yes, dumb it down for me. otherwise, the standalone lib https://github.com/BlueWallet/SilentPayments/ does the job well, and embeds nicely into whatever js library for bitcoin you are going to use (not necessarily bitcoinjs-lib, more modern js apps choose to use scure-btc-signer instead )

no worries thanks for making me aware of scure-btc-signer also mentioned the blue wallet repo there as there was an issue open for silent payments too.

});
}
// If we have all the inputs, aSum and only the spend keys for the recipients we need to calculate the input hash and secret
else if (
Copy link
Author

Choose a reason for hiding this comment

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

this case drags in inputs (hashes) and we may not want to have this here

});
}
// If we have all the inputs, privKeys and only the spend keys for the recipients we need to calculate the Sum, the input hash and secret
else if (
Copy link
Author

Choose a reason for hiding this comment

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

same here for inputs

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.

4 participants