@bsv/sdk 1.1.1 → 1.1.2
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/package.json +1 -1
- package/src/transaction/__tests/Transaction.test.ts +2 -2
- package/docs/concepts/42.md +0 -35
- package/docs/concepts/BEEF.md +0 -38
- package/docs/concepts/CHAIN_SPV.md +0 -38
- package/docs/concepts/FEE.md +0 -35
- package/docs/concepts/HASHES.md +0 -19
- package/docs/concepts/HOW_TX.md +0 -60
- package/docs/concepts/OP.md +0 -38
- package/docs/concepts/README.md +0 -14
- package/docs/concepts/TEMPLATES.md +0 -42
- package/docs/concepts/TX_SIG.md +0 -34
- package/docs/concepts/TX_VALID.md +0 -35
- package/docs/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md +0 -91
- package/docs/examples/EXAMPLE_COMPLEX_TX.md +0 -162
- package/docs/examples/EXAMPLE_ECIES.md +0 -37
- package/docs/examples/EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md +0 -52
- package/docs/examples/EXAMPLE_FEE_MODELING.md +0 -199
- package/docs/examples/EXAMPLE_HD_WALLETS.md +0 -71
- package/docs/examples/EXAMPLE_MESSAGE_SIGNING.md +0 -63
- package/docs/examples/EXAMPLE_SCRIPT_TEMPLATES.md +0 -170
- package/docs/examples/EXAMPLE_SIMPLE_TX.md +0 -145
- package/docs/examples/EXAMPLE_TYPE_42.md +0 -108
- package/docs/examples/EXAMPLE_UTXOS_TX.md +0 -85
- package/docs/examples/EXAMPLE_VERIFYING_BEEF.md +0 -62
- package/docs/examples/EXAMPLE_VERIFYING_ROOTS.md +0 -97
- package/docs/examples/EXAMPLE_VERIFYING_SPENDS.md +0 -69
- package/docs/examples/GETTING_STARTED_NODE_CJS.md +0 -71
- package/docs/examples/GETTING_STARTED_REACT.md +0 -119
- package/docs/examples/README.md +0 -19
- package/docs/low-level/AES_SYMMETRIC_ENCRYPTION.md +0 -40
- package/docs/low-level/ECDH.md +0 -64
- package/docs/low-level/NUMBERS_POINTS.md +0 -116
- package/docs/low-level/README.md +0 -13
- package/docs/low-level/TX_SIG.md +0 -132
- package/docs/low-level/TYPE_42.md +0 -53
- package/docs/low-level/USING_ECDSA.md +0 -30
- package/docs/low-level/USING_HASHES_AND_HMACS.md +0 -79
- package/docs/low-level/USING_PRIVATE_PUBLIC_KEYS.md +0 -70
- package/docs/low-level/USING_SCRIPTS.md +0 -71
- package/docs/low-level/images/symmetric_encryption_diagram.png +0 -0
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# Example: Creating Transactions with Inputs, Outputs and Templates
|
|
2
|
-
|
|
3
|
-
In Bitcoin, transactions contain inputs and outputs. The outputs are locked with scripts, and the inputs redeem these scripts by providing the correct unlocking solutions. This guide will show you how to create transactions that make use of custom inputs, outputs, and the associated script templates. For a more straightforward example, check out [how to create a simpler transaction](./EXAMPLE_SIMPLE_TX.md).
|
|
4
|
-
|
|
5
|
-
## Transaction Input and Outputs
|
|
6
|
-
|
|
7
|
-
All Bitcoins are locked up in Bitcoin transaction outputs. These outputs secure the coins by setting constraints on how they can be consumed in future transaction inputs. This security mechanism makes use of "scripts" — programs written in a special predicate language. There are many types of locking programs, embodying the multitude of BSV use-cases. The BSV SDK ships with a script templating system, making it easy for developers to create various types of scripts and abstracting away the complexity for end-users. You can learn about script templates [in the example](./EXAMPLE_SCRIPT_TEMPLATES.md).
|
|
8
|
-
|
|
9
|
-
## Creating a Transaction
|
|
10
|
-
|
|
11
|
-
To create a transaction with the SDK, you can either use the constructor:
|
|
12
|
-
|
|
13
|
-
- Use the constructor and pass in arrays of inputs and outputs, or
|
|
14
|
-
- Construct a blank transaction, then call the `addInput` and `addOutput` methods
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
const tx = new Transaction(version, inputsArray, outputsArray, lockTime)
|
|
18
|
-
// or
|
|
19
|
-
const tx = new Transaction()
|
|
20
|
-
.addInput(inputA)
|
|
21
|
-
.addInput(inputB)
|
|
22
|
-
.addOutput(outputA)
|
|
23
|
-
tx.version = version
|
|
24
|
-
tx.lockTime = lockTime
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Note that the version and lock time parameters are optional.
|
|
28
|
-
|
|
29
|
-
## Adding Inputs and Outputs
|
|
30
|
-
|
|
31
|
-
When constructing a Bitcoin transaction, inputs and outputs form the core components that dictate the flow of bitcoins. Here’s how to structure and add them to a transaction:
|
|
32
|
-
|
|
33
|
-
### Transaction Inputs
|
|
34
|
-
|
|
35
|
-
An input in a Bitcoin transaction represents the bitcoins being spent. It's essentially a reference to a previous transaction's output. Inputs have several key components:
|
|
36
|
-
|
|
37
|
-
- **`sourceTransaction` or `sourceTXID`**: A reference to either the source transaction (another Transaction instance), its TXID. Referencing the transaction itself is always preferred because it exposes more information to the library about the outputs it contains.
|
|
38
|
-
- **`sourceOutputIndex`**: A zero-based index indicating which output of the referenced transaction is being spent.
|
|
39
|
-
- **`sequence`**: A sequence number for the input. It allows for the replacement of the input until a transaction is finalized. If omitted, the final sequence number is used.
|
|
40
|
-
- **`unlockingScript`**: This script proves the spender's right to access the bitcoins from the spent output. It typically contains a digital signature and, optionally, other data like public keys.
|
|
41
|
-
- **`unlockingScriptTemplate`**: A template that provides a method to dynamically generate the unlocking script.
|
|
42
|
-
|
|
43
|
-
Next, we'll add an R-puzzle input into this transaction using the `RPuzzle`` template.
|
|
44
|
-
|
|
45
|
-
#### Example: Adding an Input
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
const sourceTransaction = Transaction.fromHex('...')
|
|
49
|
-
const puz = new RPuzzle()
|
|
50
|
-
const k = new BigNumber(1)
|
|
51
|
-
const unlockingScriptTemplate = puz.unlock(k)
|
|
52
|
-
let txInput = {
|
|
53
|
-
sourceTransaction,
|
|
54
|
-
sourceOutputIndex: 0,
|
|
55
|
-
unlockingScriptTemplate
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const myTx = new Transaction()
|
|
59
|
-
myTx.addInput(txInput)
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Transaction Outputs
|
|
63
|
-
|
|
64
|
-
Outputs define where the bitcoins are going and how they are locked until the next spend. Each output includes:
|
|
65
|
-
|
|
66
|
-
- **`satoshis`**: The amount of satoshis (the smallest unit of Bitcoin) being transferred. This value dictates how much value the output holds.
|
|
67
|
-
- **`lockingScript`**: A script that sets the conditions under which the output can be spent. It's a crucial security feature, often requiring a digital signature that matches the recipient's public key.
|
|
68
|
-
- **`change`**: An optional boolean flag indicating if the output is sending change back to the sender.
|
|
69
|
-
|
|
70
|
-
We will now add an R-puzzle output to a transaction, making use of the script template.
|
|
71
|
-
|
|
72
|
-
#### Example: Adding an Output
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
// We must first obtain an R-value for the template
|
|
76
|
-
const pubkey = PublicKey.fromString('...')
|
|
77
|
-
pubkey.x.umod(c.n).toArray()
|
|
78
|
-
r = r[0] > 127 ? [0, ...r] : r
|
|
79
|
-
const puz = new RPuzzle()
|
|
80
|
-
const lockingScript = puz.lock(r)
|
|
81
|
-
|
|
82
|
-
let txOutput = {
|
|
83
|
-
satoshis: 1000, // Amount in satoshis
|
|
84
|
-
lockingScript,
|
|
85
|
-
change: false // Not a change output, it has a defined number of satoshis
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
myTx.addOutput(txOutput)
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Change and Fee Computation
|
|
92
|
-
|
|
93
|
-
The transaction fee is the difference between the total inputs and total outputs of a transaction. Miners collect these fees as a reward for including transactions in a block. The amount of the fee paid will determine the quality of service provided my miners, subject to their policies.
|
|
94
|
-
|
|
95
|
-
If the total value of the inputs exceeds the total value you wish to send (plus the transaction fee), the excess amount is returned to you as "change." Change is sent back to a destination controlled by the sender, ensuring that no value is lost. When you set the `change` property on an output to `true`, you don't need to define a number of satoshis. This is because the library computes the number of satoshis for you, when the `.fee()` method is called.
|
|
96
|
-
|
|
97
|
-
In summary:
|
|
98
|
-
|
|
99
|
-
1. After all funding sources and recipient outputs are added, add at least one output where `change` is `true`, so that you capture what's left over after you send. Set up a locking script you control so that you can later spend your change.
|
|
100
|
-
|
|
101
|
-
2. Then, call the `.fee()` method to compute the change amounts across all change outputs, and leave the rest to the miner. You can specify a custom fee model if you wish, but the default should suffice for most use-cases.
|
|
102
|
-
|
|
103
|
-
In our above code, we already added a change output — now, we can just compute the fees before transaction signing.
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
// Compute the correct amounts for change outputs and leave the rest for the Bitcoin miners
|
|
107
|
-
myTx.fee()
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Signing and Signature Validity
|
|
111
|
-
|
|
112
|
-
Once you've defined your inputs and outputs, and once your change has been computed, the next step is to sign your transaction. There are a few things you should note when signing:
|
|
113
|
-
|
|
114
|
-
- Only inputs with an unlocking template will be signed. If you provided an unlocking script yourself, the library assumes the signatures are already in place.
|
|
115
|
-
- If you change the inputs or outputs after signing, certain signatures will need to be re-computd, depending on the SIGHASH flags used.
|
|
116
|
-
- If your templates support it, you can produce partial signatures before serializing and sending to other parties. This is especially useful for multi-signature use-cases.
|
|
117
|
-
|
|
118
|
-
With these considerations in mind, we can now sign our transaction. The `RPuzzle` unlocking templates we configured earlier will be used in this process.
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// Set the input unlocking scripts based on the script templates
|
|
122
|
-
myTx.sign()
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Serialization and Broadcast
|
|
126
|
-
|
|
127
|
-
After a transaction is signed, it can be broadcast to the BSV Mining Network, or to relevant Overlay Networks through the SDK.
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
await tx.broadcast()
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Alternatively, if you don't want to use the SDK's built-in broadcasting system, you can simply serialize your transaction into a hex string as follows:
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
// Serialize your transaction
|
|
137
|
-
myTx.toHex()
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## SPV and Serialization Formats
|
|
141
|
-
|
|
142
|
-
Simplified Payment Verification is a mechanism that enables the recipient of a transaction to verify its legitimacy by providing necessary information, like input transactions and their associated merkle proofs.
|
|
143
|
-
|
|
144
|
-
Earlier in this guide, we mentioned that you can either reference a `sourceTXID` or, preferably, a `sourceTransaction` when linking transaction inputs. The reason why it's preferable to link the entire source transaction is because serializing the transaction in an SPV-compliant way generally requires more information about the outputs being spent.
|
|
145
|
-
|
|
146
|
-
When properly linked, you can serialize your transactions in the SPV formats as follows:
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// Note: Requires use of sourceTransaction instead of sourceTXID for inputs
|
|
150
|
-
myTx.toHexBEEF()
|
|
151
|
-
// or
|
|
152
|
-
myTx.toHexEF()
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
This enables the transactions to be verified properly by recipients, using the `.verify()` method:
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
const incomingTX = Transaction.fromHexBEEF('...')
|
|
159
|
-
incomingTX.verify() // Provide a source of BSV block headers to verify
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
Recipients, with nothing other than a source of BSV block headers, can verify that the transaction properly unlocks and redeems its inputs, thereby creating its outputs. To learn more about setting up a chain tracker with a source of block headers, check out the Pulse example (link to be provided once completed).
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Example: Using ECIES Encryption
|
|
2
|
-
|
|
3
|
-
Electrum ECIES is a protocol for exchanging encrypted data between parties. It has been commonly used in many applications, and while the SDK's native [Message Encryption functionality](EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md) is the preferred approach for new applications (due to its use of GCM over CBC and aditional layers of security described below), legacy systems still use ECIES and this guide will demonstrate how it can be done.
|
|
4
|
-
|
|
5
|
-
## Message Encryption
|
|
6
|
-
|
|
7
|
-
In ECIES, a message can be encrypted directly to the public key of the recipient, either from your private key or from a random private key. The public key can either be included or excluded from the message. Check out the below examples:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { ECIES, PrivateKey, Utils } from '@bsv/sdk'
|
|
11
|
-
|
|
12
|
-
const alicePrivateKey = PrivateKey.fromString('77e06abc52bf065cb5164c5deca839d0276911991a2730be4d8d0a0307de7ceb', 16)
|
|
13
|
-
const bobPrivateKey = PrivateKey.fromString('2b57c7c5e408ce927eef5e2efb49cfdadde77961d342daa72284bb3d6590862d', 16)
|
|
14
|
-
const message = Utils.toArray('this is my ECDH test message', 'utf8')
|
|
15
|
-
|
|
16
|
-
const ecdhMessageEncryptedAlice = ECIES.electrumEncrypt(message, alicePrivateKey.toPublicKey(), bobPrivateKey, true)
|
|
17
|
-
console.log(Utils.toUTF8(ECIES.electrumDecrypt(ecdhMessageEncryptedAlice, bobPrivateKey, alicePrivateKey.toPublicKey())))
|
|
18
|
-
// 'this is my ECDH test message'
|
|
19
|
-
|
|
20
|
-
const ecdhMessageEncryptedBob = ECIES.electrumEncrypt(message, bobPrivateKey.toPublicKey(), alicePrivateKey, true)
|
|
21
|
-
console.log(Utils.toUTF8(ECIES.electrumDecrypt(ecdhMessageEncryptedBob, alicePrivateKey, bobPrivateKey.toPublicKey())))
|
|
22
|
-
// 'this is my ECDH test message'
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Here, we start by declaring two private keys. Then, ...
|
|
26
|
-
|
|
27
|
-
## Considerations
|
|
28
|
-
|
|
29
|
-
This guide has shown how to use Electrum ECIES encryption. While this approach has been used by many legacy systems, the SDK's native encryption has the following benefits:
|
|
30
|
-
|
|
31
|
-
- **Additional Security Layer**: The native SDK implentation, based on [BRC-78](https://github.com/bitcoin-sv/BRCs/blob/master/peer-to-peer/0078.md), employs an additional layer of security by utilizing a one-off ephemeral key for the encryption process. Even if the key for a particular message is discovered, it does not compromise the private keys of either of the parties. Different keys are used for every message, adding an additional step for attackers.
|
|
32
|
-
|
|
33
|
-
- **Incompatibility with BRC-43 Invoice Numbers**: The native approach is fully compatible with [BRC-43](https://brc.dev/43) invoice numbers, and the [BRC-2](https://brc.dev/2) encryption process, making it possible for users of the [BRC-56 standard wallet](https://brc.dev/56) able to natively use the system under their MetaNet identities. ECIES is not compatible with these standards.
|
|
34
|
-
|
|
35
|
-
- **Use of GCM over CBC**: While this is not a security risk, GCM supports range-based encryption and decryption. This may make it better than CBC if you need to send parts of a large encrypted dataset over the network.
|
|
36
|
-
|
|
37
|
-
Despite these drawbacks, Electrum ECIES still remains a fundamentally secure and robust encryption scheme.
|
|
@@ -1,52 +0,0 @@
|
|
|
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.
|
|
@@ -1,199 +0,0 @@
|
|
|
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!
|
|
@@ -1,71 +0,0 @@
|
|
|
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 = 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 = 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 = 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).
|
|
@@ -1,63 +0,0 @@
|
|
|
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.
|