@bsv/sdk 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/transaction/MerklePath.js +2 -1
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/transaction/MerklePath.js +2 -1
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/transaction/MerklePath.ts +2 -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,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.
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# Example: Creating the R-puzzle Script Template
|
|
2
|
-
|
|
3
|
-
This guide will provide information about the structure and functionality of script templates within the BSV SDK. Script templates are a powerful abstraction layer designed to simplify the creation and management of the scripts used in Bitcoin transactions. By understanding how these templates work, developers can leverage them to build more sophisticated and efficient blockchain applications. By the end of this example, you'll understand how the R-puzzle script template (P2RPH) was created.
|
|
4
|
-
|
|
5
|
-
### Understanding Script Templates
|
|
6
|
-
|
|
7
|
-
A script template is essentially a blueprint for creating the locking and unlocking scripts that are crucial for securing and spending bitcoins. These templates encapsulate the logic needed to construct these scripts dynamically, based on the parameters passed to them. This approach allows for a modular and reusable codebase, where common scripting patterns can be defined once and then instantiated as needed across different transactions.
|
|
8
|
-
|
|
9
|
-
#### Locking Script
|
|
10
|
-
|
|
11
|
-
The locking script, or output script, specifies the conditions under which the bitcoins can be spent. In the BSV SDK, the `lock` function of a script template is responsible for generating this script. By abstracting the creation of locking scripts into a method that accepts parameters, developers can easily create diverse conditions for spending bitcoins without having to write the low-level script code each time.
|
|
12
|
-
|
|
13
|
-
For example, a locking script might require the presentation of a public key that matches a certain hash or the fulfillment of a multi-signature condition. The flexibility of passing parameters to the `lock` function enables the creation of locking scripts tailored to specific requirements. This example will require a signature created with a particular ephemeral K-value, [an R-puzzle](https://wiki.bitcoinsv.io/index.php/R-Puzzles).
|
|
14
|
-
|
|
15
|
-
#### Unlocking Script
|
|
16
|
-
|
|
17
|
-
The unlocking script, or input script, provides the evidence needed to satisfy the conditions set by the locking script. The `unlock` method in a script template not only generates this script but also offers two key functionalities — it's a function that returns an object with two properties:
|
|
18
|
-
|
|
19
|
-
1. **`estimateLength`**: Before a transaction is signed and broadcast to the network, it's crucial to estimate its size to calculate the required fee accurately. The `estimateLength` function predicts the length of the unlocking script once it will be created, allowing developers to make informed decisions about fee estimation.
|
|
20
|
-
|
|
21
|
-
2. **`sign`**: This function generates an unlocking script that includes the necessary signatures or data required to unlock the bitcoins. By accepting a transaction and an input index as arguments, it ensures that the unlocking script is correctly associated with the specific transaction input it intends to fund, allowing signatures to be scoped accordingly.
|
|
22
|
-
|
|
23
|
-
### Creating a Script Template
|
|
24
|
-
|
|
25
|
-
To create a script template, developers define a class that adheres to the `ScriptTemplate` interface. This involves implementing the `lock` and `unlock` methods with the specific logic needed for their application.
|
|
26
|
-
|
|
27
|
-
Now that you understand the necessary components, here's the code for the R-puzzle script template:
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
import {
|
|
31
|
-
OP, ScriptTemplate, LockingScript, UnlockingScript, Transaction,
|
|
32
|
-
PrivateKey, TransactionSignature, sha256, ScriptChunk, BigNumber
|
|
33
|
-
} from '@bsv/sdk'
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* RPuzzle class implementing ScriptTemplate.
|
|
37
|
-
*
|
|
38
|
-
* This class provides methods to create R Puzzle and R Puzzle Hash locking and unlocking scripts, including the unlocking of UTXOs with the correct K value.
|
|
39
|
-
*/
|
|
40
|
-
export default class RPuzzle implements ScriptTemplate {
|
|
41
|
-
type: 'raw' | 'SHA1' | 'SHA256' | 'HASH256' | 'RIPEMD160' | 'HASH160' = 'raw'
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @constructor
|
|
45
|
-
* Constructs an R Puzzle template instance for a given puzzle type
|
|
46
|
-
*
|
|
47
|
-
* @param {'raw'|'SHA1'|'SHA256'|'HASH256'|'RIPEMD160'|'HASH160'} type Denotes the type of puzzle to create
|
|
48
|
-
*/
|
|
49
|
-
constructor (type: 'raw' | 'SHA1' | 'SHA256' | 'HASH256' | 'RIPEMD160' | 'HASH160' = 'raw') {
|
|
50
|
-
this.type = type
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Creates an R puzzle locking script for a given R value or R value hash.
|
|
55
|
-
*
|
|
56
|
-
* @param {number[]} value - An array representing the R value or its hash.
|
|
57
|
-
* @returns {LockingScript} - An R puzzle locking script.
|
|
58
|
-
*/
|
|
59
|
-
lock (value: number[]): LockingScript {
|
|
60
|
-
const chunks: ScriptChunk[] = [
|
|
61
|
-
{ op: OP.OP_OVER },
|
|
62
|
-
{ op: OP.OP_3 },
|
|
63
|
-
{ op: OP.OP_SPLIT },
|
|
64
|
-
{ op: OP.OP_NIP },
|
|
65
|
-
{ op: OP.OP_1 },
|
|
66
|
-
{ op: OP.OP_SPLIT },
|
|
67
|
-
{ op: OP.OP_SWAP },
|
|
68
|
-
{ op: OP.OP_SPLIT },
|
|
69
|
-
{ op: OP.OP_DROP }
|
|
70
|
-
]
|
|
71
|
-
if (this.type !== 'raw') {
|
|
72
|
-
chunks.push({
|
|
73
|
-
op: OP['OP_' + this.type]
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
chunks.push({ op: value.length, data: value })
|
|
77
|
-
chunks.push({ op: OP.OP_EQUALVERIFY })
|
|
78
|
-
chunks.push({ op: OP.OP_CHECKSIG })
|
|
79
|
-
return new LockingScript(chunks)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Creates a function that generates an R puzzle unlocking script along with its signature and length estimation.
|
|
84
|
-
*
|
|
85
|
-
* The returned object contains:
|
|
86
|
-
* 1. `sign` - A function that, when invoked with a transaction and an input index,
|
|
87
|
-
* produces an unlocking script suitable for an R puzzle locked output.
|
|
88
|
-
* 2. `estimateLength` - A function that returns the estimated length of the unlocking script in bytes.
|
|
89
|
-
*
|
|
90
|
-
* @param {BigNumber} k — The K-value used to unlock the R-puzzle.
|
|
91
|
-
* @param {PrivateKey} privateKey - The private key used for signing the transaction. If not provided, a random key will be generated.
|
|
92
|
-
* @param {'all'|'none'|'single'} signOutputs - The signature scope for outputs.
|
|
93
|
-
* @param {boolean} anyoneCanPay - Flag indicating if the signature allows for other inputs to be added later.
|
|
94
|
-
* @returns {Object} - An object containing the `sign` and `estimateLength` functions.
|
|
95
|
-
*/
|
|
96
|
-
unlock (
|
|
97
|
-
k: BigNumber,
|
|
98
|
-
privateKey: PrivateKey,
|
|
99
|
-
signOutputs: 'all' | 'none' | 'single' = 'all',
|
|
100
|
-
anyoneCanPay: boolean = false
|
|
101
|
-
): {
|
|
102
|
-
sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
|
|
103
|
-
estimateLength: () => Promise<106>
|
|
104
|
-
} {
|
|
105
|
-
return {
|
|
106
|
-
sign: async (tx: Transaction, inputIndex: number) => {
|
|
107
|
-
if (typeof privateKey === 'undefined') {
|
|
108
|
-
privateKey = PrivateKey.fromRandom()
|
|
109
|
-
}
|
|
110
|
-
let signatureScope = TransactionSignature.SIGHASH_FORKID
|
|
111
|
-
if (signOutputs === 'all') {
|
|
112
|
-
signatureScope |= TransactionSignature.SIGHASH_ALL
|
|
113
|
-
}
|
|
114
|
-
if (signOutputs === 'none') {
|
|
115
|
-
signatureScope |= TransactionSignature.SIGHASH_NONE
|
|
116
|
-
}
|
|
117
|
-
if (signOutputs === 'single') {
|
|
118
|
-
signatureScope |= TransactionSignature.SIGHASH_SINGLE
|
|
119
|
-
}
|
|
120
|
-
if (anyoneCanPay) {
|
|
121
|
-
signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY
|
|
122
|
-
}
|
|
123
|
-
const otherInputs = [...tx.inputs]
|
|
124
|
-
const [input] = otherInputs.splice(inputIndex, 1)
|
|
125
|
-
if (typeof input.sourceTransaction !== 'object') {
|
|
126
|
-
throw new Error(
|
|
127
|
-
'The source transaction is needed for transaction signing.'
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
const preimage = TransactionSignature.format({
|
|
131
|
-
sourceTXID: input.sourceTransaction.id('hex') as string,
|
|
132
|
-
sourceOutputIndex: input.sourceOutputIndex,
|
|
133
|
-
sourceSatoshis: input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis,
|
|
134
|
-
transactionVersion: tx.version,
|
|
135
|
-
otherInputs,
|
|
136
|
-
inputIndex,
|
|
137
|
-
outputs: tx.outputs,
|
|
138
|
-
inputSequence: input.sequence,
|
|
139
|
-
subscript: input.sourceTransaction.outputs[input.sourceOutputIndex].lockingScript,
|
|
140
|
-
lockTime: tx.lockTime,
|
|
141
|
-
scope: signatureScope
|
|
142
|
-
})
|
|
143
|
-
const rawSignature = privateKey.sign(sha256(preimage), undefined, true, k)
|
|
144
|
-
const sig = new TransactionSignature(
|
|
145
|
-
rawSignature.r,
|
|
146
|
-
rawSignature.s,
|
|
147
|
-
signatureScope
|
|
148
|
-
)
|
|
149
|
-
const sigForScript = sig.toChecksigFormat()
|
|
150
|
-
const pubkeyForScript = privateKey.toPublicKey().encode(true) as number[]
|
|
151
|
-
return new UnlockingScript([
|
|
152
|
-
{ op: sigForScript.length, data: sigForScript },
|
|
153
|
-
{ op: pubkeyForScript.length, data: pubkeyForScript }
|
|
154
|
-
])
|
|
155
|
-
},
|
|
156
|
-
estimateLength: async () => {
|
|
157
|
-
// public key (1+33) + signature (1+71)
|
|
158
|
-
// Note: We add 1 to each element's length because of the associated OP_PUSH
|
|
159
|
-
return 106
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
In this example, `RPuzzle` defines custom logic for creating both locking and unlocking scripts. The opcodes, intermixed with the various template fields, enable end-users to implement R-puzzles into their applications without being concerned with these low-level details. Check out [this guide](./EXAMPLE_COMPLEX_TX.md) to see an example of this template used in a transaction.
|
|
167
|
-
|
|
168
|
-
### Conclusion
|
|
169
|
-
|
|
170
|
-
Script templates in the BSV SDK offer a structured and efficient way to handle the creation of locking and unlocking scripts in Bitcoin transactions. By encapsulating the logic for script generation and providing essential functionalities like signature creation and length estimation, script templates make it easier for developers to implement complex transactional logic. With these tools, template consumers can focus on the higher-level aspects of their blockchain applications, relying on the SDK to manage the intricacies of script handling.
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# Example: Creating a Simple Transaction
|
|
2
|
-
|
|
3
|
-
This guide walks you through the steps of creating a simple Bitcoin transaction. To get started, let's explain some basic concepts around Bitcoin transactions.
|
|
4
|
-
|
|
5
|
-
## Understanding and Creating Transactions
|
|
6
|
-
|
|
7
|
-
Transactions in Bitcoin are mechanisms for transferring value and invoking smart contract logic. The `Transaction` class in the BSV SDK encapsulates the creation, signing, and broadcasting of transactions, also enabling the use of Bitcoin's scripting language for locking and unlocking coins.
|
|
8
|
-
|
|
9
|
-
## Creating and Signing a Transaction
|
|
10
|
-
|
|
11
|
-
Consider the scenario where you need to create a transaction. The process involves specifying inputs (where the bitcoins are coming from) and outputs (where they're going). Here's a simplified example:
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import { Transaction, PrivateKey, PublicKey, P2PKH, ARC } from '@bsv/sdk'
|
|
15
|
-
|
|
16
|
-
const privKey = PrivateKey.fromWif('...') // Your P2PKH private key
|
|
17
|
-
const changePrivKey = PrivateKey.fromWif('...') // Change private key (never re-use addresses)
|
|
18
|
-
const recipientAddress = '1Fd5F7XR8LYHPmshLNs8cXSuVAAQzGp7Hc' // Address of the recipient
|
|
19
|
-
|
|
20
|
-
const tx = new Transaction()
|
|
21
|
-
|
|
22
|
-
// Add the input
|
|
23
|
-
tx.addInput({
|
|
24
|
-
sourceTransaction: Transaction.fromHex('...'), // The source transaction where the output you are spending was created,
|
|
25
|
-
sourceOutputIndex: 0, // The output index in the source transaction
|
|
26
|
-
unlockingScriptTemplate: new P2PKH().unlock(privKey), // The script template you are using to unlock the output, in this case P2PKH
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
// Pay an output to a recipient using the P2PKH locking template
|
|
30
|
-
tx.addOutput({
|
|
31
|
-
lockingScript: new P2PKH().lock(recipientAddress),
|
|
32
|
-
satoshis: 2500
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
// Send remainder back the change
|
|
36
|
-
tx.addOutput({
|
|
37
|
-
lockingScript: new P2PKH().lock(changePrivKey.toPublicKey().toHash()),
|
|
38
|
-
change: true
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// Now we can compute the fee and sign the transaction
|
|
42
|
-
await tx.fee()
|
|
43
|
-
await tx.sign()
|
|
44
|
-
|
|
45
|
-
// Finally, we broadcast it with ARC.
|
|
46
|
-
// get your api key from https://console.taal.com
|
|
47
|
-
const apiKey = 'mainnet_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' // replace
|
|
48
|
-
await tx.broadcast()
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
This code snippet demonstrates creating a transaction, adding an input and an output, setting a change script, configuring the fee, signing the transaction, and broadcasting with the ARC broadcaster. It uses the P2PKH Template, which is a specific type of Bitcoin locking program. To learn more about templates, check out this example (link to be provided once cmpplete).
|
|
52
|
-
|
|
53
|
-
## Handling Hex Locking Scripts
|
|
54
|
-
|
|
55
|
-
Moving beyond this basic example into more advanced use-cases enables you to start dealing with custom scripts. If you're provided with a hex-encoded locking script for an output, you can set it directly in the transaction's output as follows:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
transaction.addOutput({
|
|
59
|
-
lockingScript: Script.fromHex('76a9....88ac'), // Hex-encoded locking script
|
|
60
|
-
satoshis: 2500 // Number of satoshis
|
|
61
|
-
})
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
The `Transaction` class abstracts the complexity of Bitcoin's transaction structure. It handles inputs, outputs, scripts, and serialization, offering methods to easily modify and interrogate the transaction. Check out the full code-level documentation, refer to other examples, or reach out to the community to learn more.
|
|
65
|
-
|
|
66
|
-
## Choosing broadcasting target (ARC or WhatsOnChain)
|
|
67
|
-
|
|
68
|
-
### ARC
|
|
69
|
-
|
|
70
|
-
You can broadcast via selected ARC by providing ARC instance to the `broadcast` method.
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
await tx.broadcast(new ARC('https://api.taal.com/arc', apiKey))
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### WhatsOnChain
|
|
77
|
-
|
|
78
|
-
You can broadcast via What's On Chain by providing WhatsOnChainBroadcaster instance to the `broadcast` method.
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
const network = 'main' // or 'test'
|
|
82
|
-
await tx.broadcast(new WhatsOnChainBroadcaster(network))
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Use custom http client for broadcasting
|
|
86
|
-
|
|
87
|
-
The ARC broadcaster requires an HTTP client to broadcast transactions. By default, the SDK will try to search for `window.fetch` in browser or `https` module on Node.js.
|
|
88
|
-
If you want to use a custom (or preconfigured) HTTP client, you can pass it with use of the adapter the ARC constructor:
|
|
89
|
-
|
|
90
|
-
### fetch
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// In this example we're assuming you have variable fetch holding the fetch function`
|
|
94
|
-
|
|
95
|
-
const arc = new ARC('https://api.taal.com/arc', apiKey, new FetchHttpClient(mockFetch))
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### https
|
|
99
|
-
|
|
100
|
-
Because ARC is assuming concrete interface of the http client, we're providing an adapter for https module.
|
|
101
|
-
You can use it as follows:
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
// In this example we're assuming you have variable https holding the https module loaded for example with `require('https')`
|
|
105
|
-
|
|
106
|
-
const arc = new ARC('https://api.taal.com/arc', apiKey, new NodejsHttpClient(https))
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### axios
|
|
111
|
-
|
|
112
|
-
Although the SDK is not providing adapters for axios, it can be easily used with the ARC broadcaster.
|
|
113
|
-
You can make your own "adapter" for axios as follows:
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
const axiosHttpClient = const httpClient: HttpClient = {
|
|
117
|
-
request: async (...args) => {
|
|
118
|
-
let res;
|
|
119
|
-
try {
|
|
120
|
-
res = await axios(...args);
|
|
121
|
-
} catch (e: unknown) {
|
|
122
|
-
if (axios.isAxiosError(e)) {
|
|
123
|
-
res = e.response;
|
|
124
|
-
} else {
|
|
125
|
-
throw e;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (!res) {
|
|
129
|
-
throw new Error('No response');
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
ok: res.status >= 200 && res.status <= 299,
|
|
133
|
-
...res
|
|
134
|
-
};
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
new ARC('https://api.taal.com/arc', apiKey, axiosHttpClient)
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### other libraries
|
|
142
|
-
|
|
143
|
-
Although the SDK is not providing adapters for other libraries,
|
|
144
|
-
you can easily create your own adapter by implementing the `HttpClient` interface.
|
|
145
|
-
Please look at the example for axios above to see how easy it can be done.
|