@shakesco/silent 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -149
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,210 +1,277 @@
|
|
|
1
1
|
# @shakesco/silent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JavaScript SDK for receiving Bitcoin privately with silent payments (BIP-352).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> Special thanks to [Ruben Somsen](https://x.com/SomsenRuben) and [Josie Bake](https://x.com/josibake) for their groundbreaking work on BIP-352.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## What It Does
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The `@shakesco/silent` SDK lets you implement Bitcoin silent payments, allowing users to:
|
|
10
|
+
|
|
11
|
+
- ✅ Share a single address for all payments
|
|
12
|
+
- ✅ Receive Bitcoin privately
|
|
13
|
+
- ✅ Maintain transaction unlinkability
|
|
14
|
+
- ✅ Avoid notification transaction fees
|
|
15
|
+
|
|
16
|
+
**Learn more:**
|
|
17
|
+
|
|
18
|
+
- [BIP-352 Specification](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki)
|
|
19
|
+
- [Silent Payments Explained](https://silentpayments.xyz/docs/explained/)
|
|
20
|
+
- [Full documentation](https://docs.shakesco.com/silent-payments/)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
10
25
|
npm i @shakesco/silent
|
|
11
26
|
```
|
|
12
27
|
|
|
13
|
-
|
|
28
|
+
## Quick Start
|
|
14
29
|
|
|
15
|
-
```
|
|
30
|
+
```javascript
|
|
16
31
|
const shakesco = require("@shakesco/silent");
|
|
17
32
|
const {
|
|
18
33
|
KeyGeneration,
|
|
19
34
|
SilentPaymentDestination,
|
|
20
35
|
SilentPaymentBuilder,
|
|
21
36
|
ECPrivateInfo,
|
|
22
|
-
Network
|
|
37
|
+
Network,
|
|
23
38
|
BitcoinScriptOutput,
|
|
24
39
|
bip32,
|
|
25
40
|
bip39,
|
|
26
41
|
} = shakesco;
|
|
27
42
|
```
|
|
28
43
|
|
|
29
|
-
|
|
44
|
+
## Integration Workflow
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
1. Generate silent payment address
|
|
47
|
+
2. Create destination address for each payment
|
|
48
|
+
3. Scan for incoming funds
|
|
49
|
+
4. Spend received funds
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
---
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
## 1. Generate Silent Payment Address
|
|
37
54
|
|
|
38
|
-
|
|
55
|
+
### From Private Keys (Recommended for Apps)
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
**Best for:** Non-wallet applications where users control their keys
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
const b_scan = ""; // Scan private key
|
|
61
|
+
const b_spend = ""; // Spend private key
|
|
62
|
+
|
|
63
|
+
const keys = KeyGeneration.fromPrivateKeys({
|
|
64
|
+
b_scan: b_scan,
|
|
65
|
+
b_spend: b_spend,
|
|
66
|
+
network: "testnet",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const silentPaymentAddress = keys.toAddress();
|
|
70
|
+
console.log(silentPaymentAddress);
|
|
52
71
|
```
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
**Pro tip:** Make users sign a message, then derive `b_scan` and `b_spend` from the [ECDSA signature](https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages#ecdsa-sign):
|
|
74
|
+
|
|
75
|
+
- Use `r` as `b_scan`
|
|
76
|
+
- Use `s` as `b_spend` (or vice versa)
|
|
77
|
+
|
|
78
|
+
This ensures cryptographically secure randomness without storing additional keys.
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
### From Mnemonic (For Wallets)
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
function main() {
|
|
60
|
-
const mnemonic = ""; // 12, 15, 24 word phrase
|
|
61
|
-
const keys = KeyGeneration.fromMnemonic(mnemonic);
|
|
62
|
-
const silentPaymentAddress = keys.toAddress();
|
|
63
|
-
console.log(silentPaymentAddress);
|
|
82
|
+
**Best for:** Wallet providers managing user funds
|
|
64
83
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
84
|
+
```javascript
|
|
85
|
+
const mnemonic = ""; // 12, 15, or 24 word phrase
|
|
86
|
+
const keys = KeyGeneration.fromMnemonic(mnemonic);
|
|
87
|
+
const silentPaymentAddress = keys.toAddress();
|
|
88
|
+
console.log(silentPaymentAddress);
|
|
71
89
|
```
|
|
72
90
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const b_spend = "";
|
|
81
|
-
const keys = KeyGeneration.fromPrivateKeys({
|
|
82
|
-
b_scan: b_scan,
|
|
83
|
-
b_spend: b_spend,
|
|
84
|
-
network: SilentNetwork.Testnet,
|
|
85
|
-
});
|
|
86
|
-
const changeSilentPaymentAddress = keys.toLabeledSilentPaymentAddress(0); //should always be zero!(https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#labels_for_change)
|
|
87
|
-
console.log(changeSilentPaymentAddress.toAddress()); // change silent payment address
|
|
88
|
-
}
|
|
91
|
+
**Alternative using HD key:**
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
95
|
+
const node = bip32.fromSeed(seed);
|
|
96
|
+
const keys = KeyGeneration.fromHd(node);
|
|
97
|
+
const silentPaymentAddress = keys.toAddress();
|
|
89
98
|
```
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
100
|
+
**Security Note:** If not using the signature-derived method, ensure you're using a cryptographically secure random number generator.
|
|
101
|
+
|
|
102
|
+
### Create a Change Address
|
|
103
|
+
|
|
104
|
+
**Critical for privacy:** Never send change to a public address after making silent payments.
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const keys = KeyGeneration.fromPrivateKeys({
|
|
108
|
+
b_scan: b_scan,
|
|
109
|
+
b_spend: b_spend,
|
|
110
|
+
network: "testnet",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Always use label 0 for change (per BIP-352 spec)
|
|
114
|
+
const changeSilentPaymentAddress = keys.toLabeledSilentPaymentAddress(0);
|
|
115
|
+
console.log(changeSilentPaymentAddress.toAddress());
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Why this matters:** If you send 10 silent payments to friends, then send change to your public address, you've exposed:
|
|
119
|
+
|
|
120
|
+
- ❌ Your own private transaction history
|
|
121
|
+
- ❌ Your friends' payment patterns
|
|
122
|
+
- ❌ Links between all 10 transactions
|
|
123
|
+
|
|
124
|
+
**Solution:** Always use a labeled silent payment address for change.
|
|
125
|
+
|
|
126
|
+
Reference: [BIP-352 Labels for Change](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#labels_for_change)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 2. Create Destination Address
|
|
131
|
+
|
|
132
|
+
Generate a unique taproot address for the payment:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
// Parse recipient's silent payment address
|
|
136
|
+
const addressPubKeys = KeyGeneration.fromAddress(silentPaymentAddress);
|
|
137
|
+
|
|
138
|
+
// Your UTXO details
|
|
139
|
+
const vinOutpoints = [
|
|
140
|
+
{
|
|
141
|
+
txid: "367e24cac43a7d77621ceb1cbc1cf4a7719fc81b05b07b38f99b043f4e8b95dc",
|
|
142
|
+
index: 1,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const pubkeys = [
|
|
147
|
+
"025c471f0e7d30d6f9095058bbaedaf13e1de67dbfcbe8328e6378d2a3bfb5cfd0",
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const UTXOPrivatekey = ""; // Your UTXO private key
|
|
151
|
+
|
|
152
|
+
// Build the destination
|
|
153
|
+
const builder = new SilentPaymentBuilder({
|
|
154
|
+
vinOutpoints: vinOutpoints,
|
|
155
|
+
pubkeys: pubkeys,
|
|
156
|
+
}).createOutputs(
|
|
157
|
+
[
|
|
158
|
+
new ECPrivateInfo(
|
|
159
|
+
UTXOPrivatekey,
|
|
160
|
+
false // Set true if output is from taproot
|
|
161
|
+
),
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
new SilentPaymentDestination({
|
|
165
|
+
amount: 1000, // Satoshis (1 BTC = 100,000,000 sats)
|
|
166
|
+
network: Network.Testnet,
|
|
167
|
+
version: 0,
|
|
168
|
+
scanPubkey: addressPubKeys.B_scan,
|
|
169
|
+
spendPubkey: addressPubKeys.B_spend,
|
|
170
|
+
}),
|
|
171
|
+
]
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Get the destination taproot address
|
|
175
|
+
const destinationAddress = builder[silentPaymentAddress][0];
|
|
176
|
+
console.log("Send 1000 sats to:", destinationAddress);
|
|
134
177
|
```
|
|
135
178
|
|
|
136
|
-
|
|
179
|
+
**What you need:**
|
|
137
180
|
|
|
138
|
-
|
|
181
|
+
- UTXO transaction ID and output index
|
|
182
|
+
- UTXO private key
|
|
183
|
+
- Amount in satoshis
|
|
184
|
+
- Recipient's scan and spend public keys (`B_scan`, `B_spend`)
|
|
139
185
|
|
|
140
|
-
|
|
141
|
-
2. Public key outputted.
|
|
142
|
-
3. Script and amount from the outputted taproot address
|
|
186
|
+
---
|
|
143
187
|
|
|
144
|
-
|
|
188
|
+
## 3. Scan for Incoming Funds
|
|
145
189
|
|
|
146
|
-
|
|
147
|
-
function main() {
|
|
148
|
-
const vinOutpoints = [
|
|
149
|
-
{
|
|
150
|
-
txid: "367e24cac43a7d77621ceb1cbc1cf4a7719fc81b05b07b38f99b043f4e8b95dc",
|
|
151
|
-
index: 1,
|
|
152
|
-
},
|
|
153
|
-
];
|
|
190
|
+
**Trade-off:** This is the main drawback of silent payments - you must scan the blockchain to detect incoming transactions.
|
|
154
191
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
192
|
+
```javascript
|
|
193
|
+
const vinOutpoints = [
|
|
194
|
+
{
|
|
195
|
+
txid: "367e24cac43a7d77621ceb1cbc1cf4a7719fc81b05b07b38f99b043f4e8b95dc",
|
|
196
|
+
index: 1,
|
|
197
|
+
},
|
|
198
|
+
];
|
|
158
199
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
200
|
+
const pubkeys = [
|
|
201
|
+
"025c471f0e7d30d6f9095058bbaedaf13e1de67dbfcbe8328e6378d2a3bfb5cfd0",
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const search = new SilentPaymentBuilder({
|
|
205
|
+
vinOutpoints: vinOutpoints,
|
|
206
|
+
pubkeys: pubkeys,
|
|
207
|
+
network: Network.Testnet,
|
|
208
|
+
}).scanOutputs(
|
|
209
|
+
keys.b_scan, // Your scan private key
|
|
210
|
+
keys.B_spend, // Your spend public key
|
|
211
|
+
[
|
|
164
212
|
new BitcoinScriptOutput(
|
|
165
213
|
"5120fdcb28bcea339a5d36d0c00a3e110b837bf1151be9e7ac9a8544e18b2f63307d",
|
|
166
214
|
BigInt(1000)
|
|
167
215
|
),
|
|
168
|
-
]
|
|
216
|
+
]
|
|
217
|
+
);
|
|
169
218
|
|
|
170
|
-
|
|
171
|
-
|
|
219
|
+
const foundOutput =
|
|
220
|
+
search[builder[keys.toAddress()][0].address.pubkey.toString("hex")].output;
|
|
221
|
+
console.log(foundOutput);
|
|
172
222
|
```
|
|
173
223
|
|
|
174
|
-
If the
|
|
224
|
+
If the output matches the taproot address → it's yours! 🎉
|
|
225
|
+
|
|
226
|
+
**What you need for scanning:**
|
|
227
|
+
|
|
228
|
+
- Transaction input's `txid` and `output_index`
|
|
229
|
+
- Public key from the output
|
|
230
|
+
- Script and amount from the taproot address
|
|
175
231
|
|
|
176
|
-
|
|
232
|
+
Learn more: [BIP-352 Scanning](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#scanning-silent-payment-eligible-transactions)
|
|
177
233
|
|
|
178
|
-
|
|
234
|
+
---
|
|
179
235
|
|
|
180
|
-
|
|
236
|
+
## 4. Spend the Funds
|
|
181
237
|
|
|
182
|
-
|
|
183
|
-
2. Public key outputted.
|
|
184
|
-
3. Receiver's spend and scan private keys.
|
|
238
|
+
Once you've confirmed funds belong to you, derive the private key:
|
|
185
239
|
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
];
|
|
240
|
+
```javascript
|
|
241
|
+
const vinOutpoints = [
|
|
242
|
+
{
|
|
243
|
+
txid: "367e24cac43a7d77621ceb1cbc1cf4a7719fc81b05b07b38f99b043f4e8b95dc",
|
|
244
|
+
index: 1,
|
|
245
|
+
},
|
|
246
|
+
];
|
|
194
247
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
248
|
+
const pubkeys = [
|
|
249
|
+
"025c471f0e7d30d6f9095058bbaedaf13e1de67dbfcbe8328e6378d2a3bfb5cfd0",
|
|
250
|
+
];
|
|
198
251
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
252
|
+
const private_key = new SilentPaymentBuilder({
|
|
253
|
+
vinOutpoints: vinOutpoints,
|
|
254
|
+
pubkeys: pubkeys,
|
|
255
|
+
}).spendOutputs(keys.b_scan, keys.b_spend);
|
|
203
256
|
|
|
204
|
-
|
|
205
|
-
}
|
|
257
|
+
console.log("Private key:", private_key);
|
|
206
258
|
```
|
|
207
259
|
|
|
208
|
-
|
|
260
|
+
**Tip:** Use this private key with [bitcoinjs-lib](https://github.com/bitcoinjs/bitcoinjs-lib) to build and sign your taproot transaction.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## That's It!
|
|
265
|
+
|
|
266
|
+
You've successfully implemented Bitcoin silent payments. Your users can now receive Bitcoin privately without address reuse.
|
|
267
|
+
|
|
268
|
+
## Documentation
|
|
269
|
+
|
|
270
|
+
For complete integration guides and examples, visit: [docs.shakesco.com/silent-payments](https://docs.shakesco.com/silent-payments/)
|
|
271
|
+
|
|
272
|
+
## Resources
|
|
209
273
|
|
|
210
|
-
|
|
274
|
+
- [BIP-352 Specification](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki) - Complete technical specification
|
|
275
|
+
- [bitcoinjs-lib](https://github.com/bitcoinjs/bitcoinjs-lib) - Build Bitcoin transactions in JavaScript
|
|
276
|
+
- [Silent Payments Explained](https://silentpayments.xyz/docs/explained/) - Protocol deep dive
|
|
277
|
+
- [ECDSA Signatures](https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages) - Learn about signature-based key derivation
|