@shakesco/silent 1.0.3 → 1.0.5
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 +20 -0
- package/classes/KeyGeneration.js +19 -0
- package/package.json +3 -2
- package/utils/label.js +99 -0
package/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# @shakesco/silent
|
2
2
|
|
3
|
+
_special credit to [Ruben Somsen](https://x.com/SomsenRuben) and [Josi Bake](https://x.com/josibake)_
|
4
|
+
|
3
5
|
## Install
|
4
6
|
|
5
7
|
To get started, install the package with your package manager.
|
@@ -68,6 +70,24 @@ function main() {
|
|
68
70
|
}
|
69
71
|
```
|
70
72
|
|
73
|
+
#### Create a change address
|
74
|
+
|
75
|
+
Create a change silent payment address that won't break privacy. Consider a scenario where you have sent 10 silent payments to friends and have sent the change to your public address. In this case, you would have compromised not only your private transactions but also those of your friends. So, let's create a change address:
|
76
|
+
|
77
|
+
```js {filename="index.js"}
|
78
|
+
function main() {
|
79
|
+
const b_scan = "";
|
80
|
+
const b_spend = "";
|
81
|
+
const keys = KeyGeneration.fromPrivateKeys({
|
82
|
+
b_scan: b_scan,
|
83
|
+
b_spend: b_spend,
|
84
|
+
network: "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
|
+
}
|
89
|
+
```
|
90
|
+
|
71
91
|
### Create a taproot address destination
|
72
92
|
|
73
93
|
Here is where you create a destination address for the user to send to a newly generated Taproot address, derived from the receiver's silent payment address generated above.
|
package/classes/KeyGeneration.js
CHANGED
@@ -10,6 +10,7 @@ const {
|
|
10
10
|
decodeBech32,
|
11
11
|
} = require("../utils/bech32");
|
12
12
|
const Network = require("../utils/network");
|
13
|
+
const { generateLabel, tweakAdd } = require("../utils/label");
|
13
14
|
const bip32 = BIP32Factory(tinysecp);
|
14
15
|
|
15
16
|
const SCAN_PATH = "m/352'/1'/0'/1'/0";
|
@@ -190,6 +191,24 @@ class KeyGeneration extends SilentPaymentAddress {
|
|
190
191
|
}
|
191
192
|
);
|
192
193
|
}
|
194
|
+
|
195
|
+
toLabeledSilentPaymentAddress(m) {
|
196
|
+
const label = generateLabel(m, this.b_scan);
|
197
|
+
|
198
|
+
const B_m = tweakAdd(
|
199
|
+
this.B_spend,
|
200
|
+
BigInt("0x" + Buffer.from(label).toString("hex"))
|
201
|
+
);
|
202
|
+
|
203
|
+
return new KeyGeneration({
|
204
|
+
b_scan: this.b_scan,
|
205
|
+
b_spend: this.b_spend,
|
206
|
+
B_scan: this.B_scan,
|
207
|
+
B_spend: B_m,
|
208
|
+
network: this.network,
|
209
|
+
version: this.version,
|
210
|
+
});
|
211
|
+
}
|
193
212
|
}
|
194
213
|
|
195
214
|
module.exports = { KeyGeneration, SilentPaymentDestination };
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@shakesco/silent",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.5",
|
4
4
|
"description": "Bitcoin Silent Payments",
|
5
5
|
"main": "index.js",
|
6
6
|
"author": "Shawn Kimtai",
|
@@ -16,7 +16,8 @@
|
|
16
16
|
"dependencies": {
|
17
17
|
"bip32": "^5.0.0-rc.0",
|
18
18
|
"bip39": "^3.1.0",
|
19
|
-
"
|
19
|
+
"bn.js": "^5.2.1",
|
20
|
+
"elliptic": "6.6.0",
|
20
21
|
"tiny-secp256k1": "^2.2.3"
|
21
22
|
}
|
22
23
|
}
|
package/utils/label.js
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
const crypto = require("crypto");
|
2
|
+
const EC = require("elliptic").ec;
|
3
|
+
const ec = new EC("secp256k1");
|
4
|
+
const BN = require("bn.js");
|
5
|
+
|
6
|
+
function privateKeyToBytes(privateKey) {
|
7
|
+
// Convert to hex string padded to 64 characters (32 bytes)
|
8
|
+
const hexString = privateKey.toString("hex").padStart(64, "0");
|
9
|
+
|
10
|
+
// Convert hex string to Uint8Array
|
11
|
+
return new Uint8Array(Buffer.from(hexString, "hex"));
|
12
|
+
}
|
13
|
+
|
14
|
+
function generateLabel(m, b_scan) {
|
15
|
+
return taggedHash(
|
16
|
+
concatBytes([privateKeyToBytes(b_scan), serUint32(m)]),
|
17
|
+
"BIP0352/Label"
|
18
|
+
);
|
19
|
+
}
|
20
|
+
|
21
|
+
function tweakAdd(publicKey, tweak) {
|
22
|
+
// Convert tweak to BN (Big Number) format
|
23
|
+
const tweakBN =
|
24
|
+
typeof tweak === "bigint" ? new BN(tweak.toString(16), 16) : new BN(tweak);
|
25
|
+
|
26
|
+
// Multiply generator point by tweak
|
27
|
+
const tweakMul = ec.g.mul(tweakBN);
|
28
|
+
|
29
|
+
// Add the original point and the tweaked generator point
|
30
|
+
return publicKey.add(tweakMul);
|
31
|
+
}
|
32
|
+
|
33
|
+
function serUint32(n) {
|
34
|
+
return toBytes(BigInt(n), 4);
|
35
|
+
}
|
36
|
+
|
37
|
+
function toBytes(val, length, order = "big") {
|
38
|
+
if (val === BigInt(0)) {
|
39
|
+
return new Array(length).fill(0);
|
40
|
+
}
|
41
|
+
|
42
|
+
const bigMaskEight = BigInt(0xff);
|
43
|
+
|
44
|
+
const byteList = new Array(length).fill(0);
|
45
|
+
|
46
|
+
for (let i = 0; i < length; i++) {
|
47
|
+
byteList[length - i - 1] = Number(val & bigMaskEight);
|
48
|
+
val = val >> BigInt(8);
|
49
|
+
}
|
50
|
+
|
51
|
+
if (order === "little") {
|
52
|
+
return byteList.reverse();
|
53
|
+
}
|
54
|
+
|
55
|
+
return byteList;
|
56
|
+
}
|
57
|
+
|
58
|
+
function taggedHash(data, tag) {
|
59
|
+
const tagHash = sha256Hash(new TextEncoder().encode(tag));
|
60
|
+
const concat = concatBytes([tagHash, tagHash, data]);
|
61
|
+
return sha256Hash(concat);
|
62
|
+
}
|
63
|
+
|
64
|
+
function sha256Hash(data) {
|
65
|
+
return crypto.createHash("sha256").update(data).digest();
|
66
|
+
}
|
67
|
+
|
68
|
+
function concatBytes(lists) {
|
69
|
+
// First make sure we're dealing with an array
|
70
|
+
if (!Array.isArray(lists)) {
|
71
|
+
throw new Error("Input must be an array of arrays");
|
72
|
+
}
|
73
|
+
|
74
|
+
// Filter out any undefined/null values first
|
75
|
+
const validLists = lists.filter((list) => list != null);
|
76
|
+
|
77
|
+
// Calculate total length
|
78
|
+
let totalLength = 0;
|
79
|
+
for (const list of validLists) {
|
80
|
+
totalLength += list.length;
|
81
|
+
}
|
82
|
+
|
83
|
+
// Create result array filled with zeros
|
84
|
+
const result = new Uint8Array(totalLength);
|
85
|
+
|
86
|
+
// Copy data
|
87
|
+
let offset = 0;
|
88
|
+
for (const list of validLists) {
|
89
|
+
result.set(list, offset);
|
90
|
+
offset += list.length;
|
91
|
+
}
|
92
|
+
|
93
|
+
return result;
|
94
|
+
}
|
95
|
+
|
96
|
+
module.exports = {
|
97
|
+
generateLabel,
|
98
|
+
tweakAdd,
|
99
|
+
};
|