@bsv/sdk 1.0.4 → 1.0.6
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/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/BigNumber.js +1 -1
- package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
- package/dist/cjs/src/primitives/Hash.js +98 -56
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/PrivateKey.js +23 -0
- package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
- package/dist/cjs/src/primitives/PublicKey.js +19 -1
- package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
- package/dist/cjs/src/primitives/SymmetricKey.js +14 -1
- package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/cjs/src/primitives/index.js +3 -1
- package/dist/cjs/src/primitives/index.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +3 -3
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/script/templates/P2PKH.js +14 -3
- package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/BigNumber.js +1 -1
- package/dist/esm/src/primitives/BigNumber.js.map +1 -1
- package/dist/esm/src/primitives/Hash.js +98 -56
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/PrivateKey.js +23 -0
- package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
- package/dist/esm/src/primitives/PublicKey.js +19 -1
- package/dist/esm/src/primitives/PublicKey.js.map +1 -1
- package/dist/esm/src/primitives/SymmetricKey.js +14 -1
- package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/esm/src/primitives/index.js +1 -0
- package/dist/esm/src/primitives/index.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +3 -3
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/script/templates/P2PKH.js +14 -3
- package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/BigNumber.d.ts +1 -1
- package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
- package/dist/types/src/primitives/Hash.d.ts +73 -34
- package/dist/types/src/primitives/Hash.d.ts.map +1 -1
- package/dist/types/src/primitives/PrivateKey.d.ts +16 -1
- package/dist/types/src/primitives/PrivateKey.d.ts.map +1 -1
- package/dist/types/src/primitives/PublicKey.d.ts +14 -2
- package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
- package/dist/types/src/primitives/SymmetricKey.d.ts +11 -0
- package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
- package/dist/types/src/primitives/index.d.ts +1 -0
- package/dist/types/src/primitives/index.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +2 -2
- package/dist/types/src/script/templates/P2PKH.d.ts +3 -3
- package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md +89 -0
- package/docs/examples/EXAMPLE_COMPLEX_TX.md +164 -0
- package/docs/examples/EXAMPLE_ECIES.md +37 -0
- package/docs/examples/EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md +52 -0
- package/docs/examples/EXAMPLE_FEE_MODELING.md +199 -0
- package/docs/examples/EXAMPLE_HD_WALLETS.md +71 -0
- package/docs/examples/EXAMPLE_MESSAGE_SIGNING.md +63 -0
- package/docs/examples/EXAMPLE_PULSE_HEADERS.md +140 -0
- package/docs/examples/EXAMPLE_SCRIPT_TEMPLATES.md +170 -0
- package/docs/examples/EXAMPLE_SIMPLE_TX.md +64 -0
- package/docs/examples/EXAMPLE_TYPE_42.md +108 -0
- package/docs/examples/EXAMPLE_VERIFYING_BEEF.md +55 -0
- package/docs/examples/EXAMPLE_VERIFYING_SPENDS.md +69 -0
- package/docs/examples/GETTING_STARTED_NODE_CJS.md +73 -0
- package/docs/examples/GETTING_STARTED_REACT.md +121 -0
- package/docs/examples/README.md +19 -0
- package/docs/low-level/README.md +6 -0
- package/docs/low-level/TX_SIG.md +129 -0
- package/docs/low-level/TYPE_42.md +0 -0
- package/docs/primitives.md +679 -566
- package/docs/script.md +4 -4
- package/package.json +1 -1
- package/src/primitives/BigNumber.ts +2 -1
- package/src/primitives/Hash.ts +118 -64
- package/src/primitives/PrivateKey.ts +28 -1
- package/src/primitives/PublicKey.ts +24 -2
- package/src/primitives/SymmetricKey.ts +17 -3
- package/src/primitives/__tests/HMAC.test.ts +2 -2
- package/src/primitives/__tests/Hash.test.ts +2 -2
- package/src/primitives/index.ts +1 -0
- package/src/primitives/utils.ts +3 -3
- package/src/script/__tests/Script.test.ts +34 -0
- package/src/script/__tests/Spend.test.ts +7 -7
- package/src/script/templates/P2PKH.ts +13 -4
- package/src/transaction/__tests/Transaction.test.ts +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Example: Message Encryption and Decryption
|
|
2
|
+
|
|
3
|
+
This guide walks you through the steps of encrypting and decrypting messages.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Understanding the ins-and-outs of message encryption and decryption is key to implementing secure communication. The implemented functions allow a sender to encrypt messages that only the intended recipient can decrypt, thus preserving the privacy of the message exchange.
|
|
8
|
+
|
|
9
|
+
### Encrypting a Message
|
|
10
|
+
To get started, you will first want to import the required functions / classes.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { PrivateKey, EncryptedMessage, Utils } from '@bsv/sdk'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Next, you will want to configure who the sender is, the recipient, and what message you would like to encrypt.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const sender = new PrivateKey(15)
|
|
20
|
+
const recipient = new PrivateKey(21)
|
|
21
|
+
const recipientPub = recipient.toPublicKey()
|
|
22
|
+
const message: number[] = Utils.toArray('Did you receive the Bitcoin payment?', 'utf8')
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Now you are ready to generate the ciphertext using the `encrypt` function. This function will return an array of bytes so you will need to convert it using `toUTF` if you would like it as a string of text.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const encrypted: number[] = EncryptedMessage.encrypt(message, sender, recipientPub)
|
|
29
|
+
const ciphertextMessage: string = Utils.toUTF8(encrypted)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Decrypting a Message
|
|
33
|
+
|
|
34
|
+
To get back your plaintext message, use the `decrypt` function and then transform it as needed.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const decrypted: number[] = EncryptedMessage.decrypt(encrypted, recipient)
|
|
38
|
+
const plaintextMessage: string = Utils.toUTF8(decrypted)
|
|
39
|
+
// console.log(plaintextMessage) -> 'Did you receive the Bitcoin payment?'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Considerations
|
|
43
|
+
|
|
44
|
+
As you leverage encryption and decryption in your applications, it's crucial to remember:
|
|
45
|
+
|
|
46
|
+
- **Key management**: Private keys should always be kept safe and never exposed. This library insures that intermediate keys generated for message encryption always use random nonces to mitigate key-reuse attack vectors.
|
|
47
|
+
|
|
48
|
+
- **Key of recipient:** The decryption stage using the recipient's key is always done on the recipient's end. As such, the recipient key used is the public key provided by the recipient, and is just generated in the above example for testing purposes.
|
|
49
|
+
|
|
50
|
+
- **Interpretation of decrypted data**: The decrypted byte array may need to be transformed back into a string, depending on the nature of the original message. There are several utility functions exported, such as toUTF8, that can be leveraged as needed.
|
|
51
|
+
|
|
52
|
+
This is just a simple example of how encryption and decryption can be implemented for secure message exchange in your applications. The exact method of implementation might vary based on your application’s specifics.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Example: Creating a Custom Transaction Fee Model
|
|
2
|
+
|
|
3
|
+
Bitcoin miners accept transactions into a block when they pay an appropriate fee. The transaction fee is simply the difference between the amounts used as input, and the amounts claimed by transaction outputs. This is to say, any amount of Bitcoins that are unclaimed (left over) after all transaction outputs have been fulfilled is given to the miner who solves the block in which the transaction is included.
|
|
4
|
+
|
|
5
|
+
To date, fees have generally been measured in satoshis per kilobyte of block space used by the transaction. However, the SDK allows you to create custom fee models that take other factors into account. This guide will show you the default fee model, and discuss how it might be customized in the future. Note that you'll need to consult with various miners if considering an alternative fee model, to make sure your transactions would still be included in the blockchain.
|
|
6
|
+
|
|
7
|
+
## Default Fee Model
|
|
8
|
+
|
|
9
|
+
The `.fee()` method on a `Transaction` object takes a fee model as an optional parameter. The function of a fee model is to compute the number of satoshis that the transaction should pay in fees. Here's the interface all fee models need to follow:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
/**
|
|
13
|
+
* Represents the interface for a transaction fee model.
|
|
14
|
+
* This interface defines a standard method for computing a fee when given a transaction.
|
|
15
|
+
*
|
|
16
|
+
* @interface
|
|
17
|
+
* @property {function} computeFee - A function that takes a Transaction object and returns a BigNumber representing the number of satoshis the transaction should cost.
|
|
18
|
+
*/
|
|
19
|
+
export default interface FeeModel {
|
|
20
|
+
computeFee: (transaction: Transaction) => Promise<number>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
In short, a fee model is an object with a `computeFee` function that, when called with a `Transaction` as its first and only parameter, will return a `Promise` for a `number` of satoshis.
|
|
26
|
+
|
|
27
|
+
The default fee model, used if no other model is provided, looks like this:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
/**
|
|
31
|
+
* Represents the "satoshis per kilobyte" transaction fee model.
|
|
32
|
+
*/
|
|
33
|
+
export default class SatoshisPerKilobyte implements FeeModel {
|
|
34
|
+
/**
|
|
35
|
+
* @property
|
|
36
|
+
* Denotes the number of satoshis paid per kilobyte of transaction size.
|
|
37
|
+
*/
|
|
38
|
+
value: number
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Constructs an instance of the sat/kb fee model.
|
|
42
|
+
*
|
|
43
|
+
* @param {number} value - The number of satoshis per kilobyte to charge as a fee.
|
|
44
|
+
*/
|
|
45
|
+
constructor(value: number) {
|
|
46
|
+
this.value = value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Computes the fee for a given transaction.
|
|
51
|
+
*
|
|
52
|
+
* @param tx The transaction for which a fee is to be computed.
|
|
53
|
+
* @returns The fee in satoshis for the transaction, as a number.
|
|
54
|
+
*/
|
|
55
|
+
async computeFee(tx: Transaction): Promise<number> {
|
|
56
|
+
const getVarIntSize = (i: number): number => {
|
|
57
|
+
if (i > 2 ** 32) {
|
|
58
|
+
return 9
|
|
59
|
+
} else if (i > 2 ** 16) {
|
|
60
|
+
return 5
|
|
61
|
+
} else if (i > 253) {
|
|
62
|
+
return 3
|
|
63
|
+
} else {
|
|
64
|
+
return 1
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Compute the (potentially estimated) size of the transaction
|
|
68
|
+
let size = 4 // version
|
|
69
|
+
size += getVarIntSize(tx.inputs.length) // number of inputs
|
|
70
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
71
|
+
const input = tx.inputs[i]
|
|
72
|
+
size += 40 // txid, output index, sequence number
|
|
73
|
+
let scriptLength: number
|
|
74
|
+
if (typeof input.unlockingScript === 'object') {
|
|
75
|
+
scriptLength = input.unlockingScript.toBinary().length
|
|
76
|
+
} else if (typeof input.unlockingScriptTemplate === 'object') {
|
|
77
|
+
scriptLength = await input.unlockingScriptTemplate.estimateLength(tx, i)
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error('All inputs must have an unlocking script or an unlocking script template for sat/kb fee computation.')
|
|
80
|
+
}
|
|
81
|
+
size += getVarIntSize(scriptLength) // unlocking script length
|
|
82
|
+
size += scriptLength // unlocking script
|
|
83
|
+
}
|
|
84
|
+
size += getVarIntSize(tx.outputs.length) // number of outputs
|
|
85
|
+
for (const out of tx.outputs) {
|
|
86
|
+
size += 8 // satoshis
|
|
87
|
+
const length = out.lockingScript.toBinary().length
|
|
88
|
+
size += getVarIntSize(length) // script length
|
|
89
|
+
size += length // script
|
|
90
|
+
}
|
|
91
|
+
size += 4 // lock time
|
|
92
|
+
// We'll use Math.ceil to ensure the miners get the extra satoshi.
|
|
93
|
+
const fee = Math.ceil((size / 1000) * this.value)
|
|
94
|
+
return fee
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Here, you can see we're computing the size of the transaction in bytes, then computing the number of satoshis based on the number of kilobytes.
|
|
100
|
+
|
|
101
|
+
## Making Adjustments
|
|
102
|
+
|
|
103
|
+
Let's modify our fee model to check for a few custom cases, just as a purely theoretical example:
|
|
104
|
+
|
|
105
|
+
- If the version of the transaction is 3301, the transaction is free.
|
|
106
|
+
- If there are more than 3x as many inputs as there are outputs (the transaction is helping shrink the number of UTXOs), the transaction gets a 20% discount.
|
|
107
|
+
- If there are more than 5x as many outputs as there are inputs, the transaction is 10% more expensive.
|
|
108
|
+
- Other than that, the rules are the same as the Satoshis per Kilobyte fee model.
|
|
109
|
+
|
|
110
|
+
With these rules in place, let's build a custom fee model!
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
/**
|
|
114
|
+
* Represents the "example" transaction fee model.
|
|
115
|
+
*/
|
|
116
|
+
export default class Example implements FeeModel {
|
|
117
|
+
/**
|
|
118
|
+
* @property
|
|
119
|
+
* Denotes the base number of satoshis paid per kilobyte of transaction size.
|
|
120
|
+
*/
|
|
121
|
+
value: number
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Constructs an instance of the example fee model.
|
|
125
|
+
*
|
|
126
|
+
* @param {number} value - The base number of satoshis per kilobyte to charge as a fee.
|
|
127
|
+
*/
|
|
128
|
+
constructor(value: number) {
|
|
129
|
+
this.value = value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Computes the fee for a given transaction.
|
|
134
|
+
*
|
|
135
|
+
* @param tx The transaction for which a fee is to be computed.
|
|
136
|
+
* @returns The fee in satoshis for the transaction, as a number.
|
|
137
|
+
*/
|
|
138
|
+
async computeFee(tx: Transaction): Promise<number> {
|
|
139
|
+
const getVarIntSize = (i: number): number => {
|
|
140
|
+
if (i > 2 ** 32) {
|
|
141
|
+
return 9
|
|
142
|
+
} else if (i > 2 ** 16) {
|
|
143
|
+
return 5
|
|
144
|
+
} else if (i > 253) {
|
|
145
|
+
return 3
|
|
146
|
+
} else {
|
|
147
|
+
return 1
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Version 3301 transactions are free :)
|
|
152
|
+
if (tx.version === 3301) {
|
|
153
|
+
return 0
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Compute the (potentially estimated) size of the transaction
|
|
157
|
+
let size = 4 // version
|
|
158
|
+
size += getVarIntSize(tx.inputs.length) // number of inputs
|
|
159
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
160
|
+
const input = tx.inputs[i]
|
|
161
|
+
size += 40 // txid, output index, sequence number
|
|
162
|
+
let scriptLength: number
|
|
163
|
+
if (typeof input.unlockingScript === 'object') {
|
|
164
|
+
scriptLength = input.unlockingScript.toBinary().length
|
|
165
|
+
} else if (typeof input.unlockingScriptTemplate === 'object') {
|
|
166
|
+
scriptLength = await input.unlockingScriptTemplate.estimateLength(tx, i)
|
|
167
|
+
} else {
|
|
168
|
+
throw new Error('All inputs must have an unlocking script or an unlocking script template for sat/kb fee computation.')
|
|
169
|
+
}
|
|
170
|
+
size += getVarIntSize(scriptLength) // unlocking script length
|
|
171
|
+
size += scriptLength // unlocking script
|
|
172
|
+
}
|
|
173
|
+
size += getVarIntSize(tx.outputs.length) // number of outputs
|
|
174
|
+
for (const out of tx.outputs) {
|
|
175
|
+
size += 8 // satoshis
|
|
176
|
+
const length = out.lockingScript.toBinary().length
|
|
177
|
+
size += getVarIntSize(length) // script length
|
|
178
|
+
size += length // script
|
|
179
|
+
}
|
|
180
|
+
size += 4 // lock time
|
|
181
|
+
let fee = ((size / 1000) * this.value)
|
|
182
|
+
|
|
183
|
+
// Now we apply our input and output rules
|
|
184
|
+
// For the inputs incentive
|
|
185
|
+
if (tx.inputs.length / 3 >= tx.outputs.length) {
|
|
186
|
+
fee *= 0.8
|
|
187
|
+
}
|
|
188
|
+
// For the outputs penalty
|
|
189
|
+
if (tx.outputs.length / 5 >= tx.inputs.length) {
|
|
190
|
+
fee *= 1.1
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// We'll use Math.ceil to ensure the miners get the extra satoshi.
|
|
194
|
+
return Math.ceil(fee)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Now. when you create a new transaction and call the `.ee()` method with this fee model, it will follow the rules we have set above!
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Example: BIP32 Key Derivation with HD Wallets
|
|
2
|
+
|
|
3
|
+
For a long time, BIP32 was the standard way to structure a Bitcoin wallet. While [type-42](EXAMPLE_TYPE_42.md) has since taken over as the standard approach due to its increased privacy and open-ended invoice numbering scheme, it's sometimes still necessary to interact with legacy systems using BIP32 key derivation.
|
|
4
|
+
|
|
5
|
+
This guide will show you how to generate keys, derive child keys, and convert them to WIF and Bitcoin address formats. At the end, we'll compare BIP32 to the [type-42 system and encourage you to adopt the new approach](EXAMPLE_TYPE_42.md) to key management.
|
|
6
|
+
|
|
7
|
+
## Generating BIP32 keys
|
|
8
|
+
|
|
9
|
+
You can generate a BIP32 seed with the SDK as follows:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { HD } from '@bsv/sdk'
|
|
13
|
+
const randomKey = HD.fromRandom()
|
|
14
|
+
|
|
15
|
+
// Convert your "xprv" key to a string
|
|
16
|
+
console.log(randomKey.toString())
|
|
17
|
+
|
|
18
|
+
// Example: xprv9s21ZrQH143K2vF2szsDFhrRehet4iHNCBPWprjymByU9mzN7n687qj3ULQ2YYXdNqFwhVSsKv9W9fM675whM9ATaYrmsLpykQSxMc6RN8V
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
You can also import an existing key as follows:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
const importedKey = new HD().fromString('xprv...')
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Now that you've generated or imported your key, you're ready to derive child keys.
|
|
28
|
+
|
|
29
|
+
## Deriving Child Keys
|
|
30
|
+
|
|
31
|
+
BIP32 child keys can be derived from a key using the `.derive()` method. Here's a full example:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const key = new bsv.HD().fromString('xprv9s21ZrQH143K2vF2szsDFhrRehet4iHNCBPWprjymByU9mzN7n687qj3ULQ2YYXdNqFwhVSsKv9W9fM675whM9ATaYrmsLpykQSxMc6RN8V')
|
|
35
|
+
const child = key.derive('m/0/1/2')
|
|
36
|
+
console.log(child.toString())
|
|
37
|
+
// 'xprv9yGx5dNDfq8pt1DJ9SFCK3gNFhjrL3kTqEj98oDs6xvfaUAUs3nyvVakQzwHAEMrc6gg1c3iaNCDubUruhX75gNHC7HAnFxHuxeiMVgLEqS'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Any of the standard derivation paths can be passed into the `.derive()` method.
|
|
41
|
+
|
|
42
|
+
## Converting Between Formats
|
|
43
|
+
|
|
44
|
+
XPRIV keys can be converted to normal `PrivateKey` instances, and from there to WIF keys. XPUB keys can be converted to normal `PublicKey` instances, and from there to Bitcoin addresses. XPRIV keys can also be converted to XPUB keys:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const key = new bsv.HD().fromString('xprv9s21ZrQH143K2vF2szsDFhrRehet4iHNCBPWprjymByU9mzN7n687qj3ULQ2YYXdNqFwhVSsKv9W9fM675whM9ATaYrmsLpykQSxMc6RN8V')
|
|
48
|
+
|
|
49
|
+
// Convert XPRIV to XPUB
|
|
50
|
+
const xpubKey = key.toPublic()
|
|
51
|
+
console.log(xpubKey.toString());
|
|
52
|
+
// xpub661MyMwAqRbcFQKVz2QDcqoACjVNUB1DZQK7dF9bKXWT2aKWfKQNfe3XKakZ1EnxeNP5E4MqZnZZw4P7179rPbeJEjhYbwF5ovkbGkeYPdF
|
|
53
|
+
|
|
54
|
+
// Convert XPRIV to WIF
|
|
55
|
+
console.log(key.privKey.toWif())
|
|
56
|
+
// L1MZHeu2yMYRpDr45icTvjN7s3bBK1o7NgsRMkcfhzgRjLzewAhZ
|
|
57
|
+
|
|
58
|
+
// Convert XPUB to public key
|
|
59
|
+
console.log(xpubKey.pubKey.toString())
|
|
60
|
+
// 022248d79bf217de60fa4afd4c7841e4f957b6459ed9a8c9c01b61e16cd4da3aae
|
|
61
|
+
|
|
62
|
+
// Convert XPUB to address
|
|
63
|
+
console.log(xpubKey.pubKey.toAddress())
|
|
64
|
+
// 1CJXwGLb6GMCF46A721VYW59b21kkoRc5D
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This guide has demonstrated how to use BIP32 for key derivation and format conversion. You can continue to use BIP32 within BSV wallet applications, but it's important to consider the disadvantages and risks of continued use, which are discussed below.
|
|
68
|
+
|
|
69
|
+
## Disadvantages and Risks
|
|
70
|
+
|
|
71
|
+
BIP32 allows anyone to derive child keys if they know an XPUB. The number of child keys per parent is limited to 2^31, and there's no support for custom invoice numbering schemes that can be used when deriving a child, only a simple integer. Finally, BIP32 has no support for private derivation, where two parties share a common key universe no one else can link to them, even while knowing the master public key. It's for these reasons that we recommend the use of type-42 over BIP32. You can read an equivalent guide [here](EXAMPLE_TYPE_42.md).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Example: Message Signing
|
|
2
|
+
|
|
3
|
+
This guide walks through the necessary steps for both public and private message signing.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Message signing is a mechanism that preserves the integrity of secure communications, enabling entities to verify the authenticity of a message's origin. This document emphasizes two primary types of message signing: private and public.
|
|
8
|
+
|
|
9
|
+
Private message signing is used when a message needs to be verified by a *specific recipient*. In this scenario, the sender creates a unique signature using their private key combined with the recipient's public key. The recipient, using their private key, can then verify the signature and thereby the message's authenticity.
|
|
10
|
+
|
|
11
|
+
On the other hand, public message signing creates a signature that can be verified by *anyone*, without the need for any specific private key. This is achieved by the sender using only their private key to create the signature.
|
|
12
|
+
|
|
13
|
+
The choice between private and public message signing hinges on the specific requirements of the communication. For applications that require secure communication where authentication is paramount, private message signing proves most effective. Conversely, when the authenticity of a message needs to be transparent to all parties, public message signing is the go-to approach. Understanding these differences will enable developers to apply the correct method of message signing depending on their specific use case.
|
|
14
|
+
|
|
15
|
+
### 1. Example Code - Private Message Signing
|
|
16
|
+
To get started, you will first want to import the required functions / classes.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { PrivateKey, SignedMessage, Utils } from '@bsv/sdk'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Next, you will want to configure who the sender is, the recipient, and what message you would like to sign.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
const sender = new PrivateKey(15)
|
|
26
|
+
const recipient = new PrivateKey(21)
|
|
27
|
+
const recipientPub = recipient.toPublicKey()
|
|
28
|
+
const message: number[] = Utils.toArray('I like big blocks and I cannot lie', 'utf8')
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Now we can sign the message and generate a signature that can only be verified by our specified recipient.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const signature = SignedMessage.sign(message, sender, recipientPub)
|
|
35
|
+
const verified = SignedMessage.verify(message, signature, recipient)
|
|
36
|
+
// console.log(verified) -> true
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Example Code - Public Message Signing
|
|
40
|
+
To create a signature that anyone can verify, the code is very similar to the first example, just without a specified recipient. This will allow anyone to verify the signature generated without requiring them to know a specific private key.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { PrivateKey, SignedMessage, Utils } from '@bsv/sdk'
|
|
44
|
+
|
|
45
|
+
const sender = new PrivateKey(15)
|
|
46
|
+
const message: number[] = Utils.toArray('I like big blocks and I cannot lie', 'utf8')
|
|
47
|
+
|
|
48
|
+
const signature = SignedMessage.sign(message, sender)
|
|
49
|
+
const verified = SignedMessage.verify(message, signature)
|
|
50
|
+
// console.log(verified) -> true
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Considerations
|
|
54
|
+
|
|
55
|
+
While these private signing functions are built on industry standards and well-tested code, there are considerations to keep in mind when integrating them into your applications.
|
|
56
|
+
|
|
57
|
+
- **Private Key Security**: Private keys must be stored securely to prevent unauthorized access, as they can be used to sign fraudulent messages.
|
|
58
|
+
|
|
59
|
+
- **Use Case Analysis**: As stated in the overview, you should carefully evaluate whether you need a private or publicly verifiable signature based on your use case.
|
|
60
|
+
|
|
61
|
+
- **Implications of Signature Verifiability**: When creating signatures that anyone can verify, consider the implications. While transparency is achieved, sensitive messages should only be verifiable by intended recipients.
|
|
62
|
+
|
|
63
|
+
By understanding and applying these considerations, you can ensure a secure implementation of private signing within your applications.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Example: Building a Pulse Block Headers Client
|
|
2
|
+
|
|
3
|
+
When [verifying BEEF structures](EXAMPLE_VERIFYING_BEEF.md), it's necessary to ensure that all transactions are well-anchored: this is to say, that they come from inputs in the honest chain. The SDK doesn't ship with a headers client, but this guide shows an example of how to use it with [Pulse](https://github.com/bitcoin-sv/block-headers-service): a popular client suitable for a wide range of use-cases.
|
|
4
|
+
|
|
5
|
+
## Pre-requisites
|
|
6
|
+
|
|
7
|
+
As stated in the README, you will need to be running a Pulse instance. Get it up and running, and configure a level of authentication appropriate for your use-case:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
docker pull bsvb/block-headers-service
|
|
11
|
+
docker run bsvb/block-headers-service:latest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Building our Client
|
|
15
|
+
|
|
16
|
+
The SDK's `ChainTracker` interface defines the required structure for our implementation, as follows:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
/**
|
|
20
|
+
* The Chain Tracker is responsible for verifying the validity of a given Merkle root
|
|
21
|
+
* for a specific block height within the blockchain.
|
|
22
|
+
*
|
|
23
|
+
* Chain Trackers ensure the integrity of the blockchain by
|
|
24
|
+
* validating new headers against the chain's history. They use accumulated
|
|
25
|
+
* proof-of-work and protocol adherence as metrics to assess the legitimacy of blocks.
|
|
26
|
+
*
|
|
27
|
+
* @interface ChainTracker
|
|
28
|
+
* @function isValidRootForHeight - A method to verify the validity of a Merkle root
|
|
29
|
+
* for a given block height.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const chainTracker = {
|
|
33
|
+
* isValidRootForHeight: async (root, height) => {
|
|
34
|
+
* // Implementation to check if the Merkle root is valid for the specified block height.
|
|
35
|
+
* }
|
|
36
|
+
* };
|
|
37
|
+
*/
|
|
38
|
+
export default interface ChainTracker {
|
|
39
|
+
isValidRootForHeight: (root: string, height: number) => Promise<boolean>
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Given a merkle root and block height, we return a boolean indicating whether it's valid.
|
|
44
|
+
|
|
45
|
+
We can plug in the Pulse API with appropriate HTTP handling logic as follows:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
/**
|
|
49
|
+
* Represents a Pulse headers client.
|
|
50
|
+
*/
|
|
51
|
+
export default class PulseClient implements ChainTracker {
|
|
52
|
+
URL: string
|
|
53
|
+
apiKey: string
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Constructs an instance of the Pulse chain tracker.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} URL - The URL endpoint for the Pulse API.
|
|
59
|
+
* @param {string} apiKey - The API key used for authorization with the Pulse API.
|
|
60
|
+
*/
|
|
61
|
+
constructor (URL: string, apiKey: string) {
|
|
62
|
+
this.URL = URL
|
|
63
|
+
this.apiKey = apiKey
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks a merkle root for a height.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} root - The merkle root to check.
|
|
70
|
+
* @param {number} height — The height at which to check.
|
|
71
|
+
* @returns {Promise<boolean>} A promise that resolves to either a success or failure response (true or false).
|
|
72
|
+
*/
|
|
73
|
+
async isValidRootForHeight (root: String, height: number): Promise<boolean> {
|
|
74
|
+
const requestOptions = {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
let response
|
|
84
|
+
let data: any = {}
|
|
85
|
+
|
|
86
|
+
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
87
|
+
// Use fetch in a browser environment
|
|
88
|
+
response = await window.fetch(`${this.URL}/api/v1/chain/header/byHeight?height=${height}&count=1`, requestOptions)
|
|
89
|
+
data = await response.json()
|
|
90
|
+
} else if (typeof require !== 'undefined') {
|
|
91
|
+
// Use Node.js https module
|
|
92
|
+
// eslint-disable-next-line
|
|
93
|
+
const https = require('https')
|
|
94
|
+
response = await this.nodeFetch(https, requestOptions)
|
|
95
|
+
data = JSON.parse(response)
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error('No method available to perform HTTP request')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (data.txid as boolean || response.ok as boolean || response.statusCode === 200) {
|
|
101
|
+
// Root must match
|
|
102
|
+
return data[0].merkleRoot === root
|
|
103
|
+
} else {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Handle error
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Helper function for Node.js HTTPS requests */
|
|
113
|
+
private async nodeFetch (https, requestOptions, height): Promise<any> {
|
|
114
|
+
return await new Promise((resolve, reject) => {
|
|
115
|
+
const req = https.request(`${this.URL}/api/v1/chain/header/byHeight?height=${height}&count=1`, requestOptions, res => {
|
|
116
|
+
let data = ''
|
|
117
|
+
res.on('data', (chunk: string) => {
|
|
118
|
+
data += chunk
|
|
119
|
+
})
|
|
120
|
+
res.on('end', () => {
|
|
121
|
+
resolve(data)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
req.on('error', error => {
|
|
126
|
+
reject(error)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (requestOptions.body as boolean) {
|
|
130
|
+
req.write(requestOptions.body)
|
|
131
|
+
}
|
|
132
|
+
req.end()
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Now, we can use our `PulseClient` as a `ChainTracker` when calling the `Transaction` object's `.verify()` method. You can see an example in the [BEEF verification guide](EXAMPLE_VERIFYING_BEEF.md).
|
|
139
|
+
|
|
140
|
+
This provides the ability to ensure that a transaction is well-anchored.
|