@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.
Files changed (86) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/BigNumber.js +1 -1
  3. package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
  4. package/dist/cjs/src/primitives/Hash.js +98 -56
  5. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  6. package/dist/cjs/src/primitives/PrivateKey.js +23 -0
  7. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  8. package/dist/cjs/src/primitives/PublicKey.js +19 -1
  9. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  10. package/dist/cjs/src/primitives/SymmetricKey.js +14 -1
  11. package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
  12. package/dist/cjs/src/primitives/index.js +3 -1
  13. package/dist/cjs/src/primitives/index.js.map +1 -1
  14. package/dist/cjs/src/primitives/utils.js +3 -3
  15. package/dist/cjs/src/primitives/utils.js.map +1 -1
  16. package/dist/cjs/src/script/templates/P2PKH.js +14 -3
  17. package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
  18. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  19. package/dist/esm/src/primitives/BigNumber.js +1 -1
  20. package/dist/esm/src/primitives/BigNumber.js.map +1 -1
  21. package/dist/esm/src/primitives/Hash.js +98 -56
  22. package/dist/esm/src/primitives/Hash.js.map +1 -1
  23. package/dist/esm/src/primitives/PrivateKey.js +23 -0
  24. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  25. package/dist/esm/src/primitives/PublicKey.js +19 -1
  26. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  27. package/dist/esm/src/primitives/SymmetricKey.js +14 -1
  28. package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
  29. package/dist/esm/src/primitives/index.js +1 -0
  30. package/dist/esm/src/primitives/index.js.map +1 -1
  31. package/dist/esm/src/primitives/utils.js +3 -3
  32. package/dist/esm/src/primitives/utils.js.map +1 -1
  33. package/dist/esm/src/script/templates/P2PKH.js +14 -3
  34. package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
  35. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  36. package/dist/types/src/primitives/BigNumber.d.ts +1 -1
  37. package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
  38. package/dist/types/src/primitives/Hash.d.ts +73 -34
  39. package/dist/types/src/primitives/Hash.d.ts.map +1 -1
  40. package/dist/types/src/primitives/PrivateKey.d.ts +16 -1
  41. package/dist/types/src/primitives/PrivateKey.d.ts.map +1 -1
  42. package/dist/types/src/primitives/PublicKey.d.ts +14 -2
  43. package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
  44. package/dist/types/src/primitives/SymmetricKey.d.ts +11 -0
  45. package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
  46. package/dist/types/src/primitives/index.d.ts +1 -0
  47. package/dist/types/src/primitives/index.d.ts.map +1 -1
  48. package/dist/types/src/primitives/utils.d.ts +2 -2
  49. package/dist/types/src/script/templates/P2PKH.d.ts +3 -3
  50. package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
  51. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  52. package/docs/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md +89 -0
  53. package/docs/examples/EXAMPLE_COMPLEX_TX.md +164 -0
  54. package/docs/examples/EXAMPLE_ECIES.md +37 -0
  55. package/docs/examples/EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md +52 -0
  56. package/docs/examples/EXAMPLE_FEE_MODELING.md +199 -0
  57. package/docs/examples/EXAMPLE_HD_WALLETS.md +71 -0
  58. package/docs/examples/EXAMPLE_MESSAGE_SIGNING.md +63 -0
  59. package/docs/examples/EXAMPLE_PULSE_HEADERS.md +140 -0
  60. package/docs/examples/EXAMPLE_SCRIPT_TEMPLATES.md +170 -0
  61. package/docs/examples/EXAMPLE_SIMPLE_TX.md +64 -0
  62. package/docs/examples/EXAMPLE_TYPE_42.md +108 -0
  63. package/docs/examples/EXAMPLE_VERIFYING_BEEF.md +55 -0
  64. package/docs/examples/EXAMPLE_VERIFYING_SPENDS.md +69 -0
  65. package/docs/examples/GETTING_STARTED_NODE_CJS.md +73 -0
  66. package/docs/examples/GETTING_STARTED_REACT.md +121 -0
  67. package/docs/examples/README.md +19 -0
  68. package/docs/low-level/README.md +6 -0
  69. package/docs/low-level/TX_SIG.md +129 -0
  70. package/docs/low-level/TYPE_42.md +0 -0
  71. package/docs/primitives.md +679 -566
  72. package/docs/script.md +4 -4
  73. package/package.json +1 -1
  74. package/src/primitives/BigNumber.ts +2 -1
  75. package/src/primitives/Hash.ts +118 -64
  76. package/src/primitives/PrivateKey.ts +28 -1
  77. package/src/primitives/PublicKey.ts +24 -2
  78. package/src/primitives/SymmetricKey.ts +17 -3
  79. package/src/primitives/__tests/HMAC.test.ts +2 -2
  80. package/src/primitives/__tests/Hash.test.ts +2 -2
  81. package/src/primitives/index.ts +1 -0
  82. package/src/primitives/utils.ts +3 -3
  83. package/src/script/__tests/Script.test.ts +34 -0
  84. package/src/script/__tests/Spend.test.ts +7 -7
  85. package/src/script/templates/P2PKH.ts +13 -4
  86. 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.