@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,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.
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Example: Using Type 42 Key Derivation for Bitcoin Wallet Management
|
|
2
|
-
|
|
3
|
-
Welcome to this type-42 key derivation guide! We're glad you're here, especially if you're migrating from an older key derivation system. Type-42 is more private, more elegant and it's easy to understand.
|
|
4
|
-
|
|
5
|
-
This guide will walk you through using type-42 keys in the context of a Bitcoin wallet.
|
|
6
|
-
|
|
7
|
-
## Generating keys
|
|
8
|
-
|
|
9
|
-
Generating type-42 keys with the SDK is identical to generating normal private keys. Secretly, every private key (and public key) in the SDK is already a type-42 key!
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import { PrivateKey } from '@bsv/sdk'
|
|
13
|
-
|
|
14
|
-
const privateKey = PrivateKey.fromRandom()
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Now, we can move on to key derivation.
|
|
18
|
-
|
|
19
|
-
## Type 42 Key Derivation
|
|
20
|
-
|
|
21
|
-
In type-42 systems, you provide a counterparty key when deriving, as well as your own. There is always one public key and one private key. It's either:
|
|
22
|
-
|
|
23
|
-
- Your private key and the public key of your counterparty are used to derive one of your private keys, or
|
|
24
|
-
- Your private key and the public key of your counterparty are used to derive one of their public keys
|
|
25
|
-
|
|
26
|
-
When you and your counterparty use the same invoice number to derive keys, the public key you derive for them will correspond to the private key they derive for themselves. A private key that you derive for yourself will correspond to the public key they derived for you.
|
|
27
|
-
|
|
28
|
-
Once you understand those concepts, we're ready to jump into some code!
|
|
29
|
-
|
|
30
|
-
### Alice and Bob
|
|
31
|
-
|
|
32
|
-
Let's consider the scenario of Alice and Bob, who want to exchange some Bitcoin. How can Alice send Bitcoins to Bob?
|
|
33
|
-
|
|
34
|
-
1. Alice learns Bob's master public key, and they agree on the Bitcoin aount to exchange.
|
|
35
|
-
2. They also agree on an invoice number.
|
|
36
|
-
3. Alice uses Bob's master public key with her private key to derive the payment key she will use.
|
|
37
|
-
4. Alice creates a Bitcoin transaction and pays Bob the money.
|
|
38
|
-
5. Bob uses Alice's public key and his own private key to derive the corresponding private key, verifying it matches the transaction Alice sent him.
|
|
39
|
-
|
|
40
|
-
Here's an example:
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
// Alice and Bob have master private keys...
|
|
44
|
-
const alice = PrivateKey.fromRandom()
|
|
45
|
-
const bob = PrivateKey.fromRandom()
|
|
46
|
-
alice.toString()
|
|
47
|
-
'106674548343907642146062962636638307981249604845652704224160905817279514790351'
|
|
48
|
-
bob.toString()
|
|
49
|
-
'108446104340374144960104248963000752145648236191076545713737995455205583156408'
|
|
50
|
-
|
|
51
|
-
// ... and master public keys
|
|
52
|
-
const alicePub = alice.toPublicKey()
|
|
53
|
-
const bobPub = bob.toPublicKey()
|
|
54
|
-
alicePub.toString()
|
|
55
|
-
'0260846fbaf8e950c1896d360954a716f26699252b879fea1743a9f78a0950d167'
|
|
56
|
-
bobPub.toString()
|
|
57
|
-
'0258bfa42bd832c4ab655295cac5e2f64daefb2b4cd9a2b72bdd3c3f9ba5076cb7'
|
|
58
|
-
|
|
59
|
-
// To pay Alice, they agree on an invoice number and then Bob derives a key where he can pay Alice
|
|
60
|
-
const paymentKey = alicePub.deriveChild(bob, 'AMZN-44-1191213')
|
|
61
|
-
|
|
62
|
-
// The key can be converted to an address if desired
|
|
63
|
-
paymentKey2.toAddress()
|
|
64
|
-
'1HqfEfHNF9ji9p3AEC66mj8fhGA7sy2WYT'
|
|
65
|
-
|
|
66
|
-
// To unlock the coins, Alice derives the private key with the same invoice number, using Bob's public key
|
|
67
|
-
const paymentPriv = alice.deriveChild(bobPub, 'AMZN-44-1191213')
|
|
68
|
-
|
|
69
|
-
// The key can be converted to WIF if desired
|
|
70
|
-
paymentPriv.toWif()
|
|
71
|
-
'L22stYh323a8DfBNunLvxrcrxudT2YXjdKxe1q9ecARYT9XfFGGc'
|
|
72
|
-
|
|
73
|
-
// To check, Alice can convert the private key back into an address.
|
|
74
|
-
paymentPriv.toPublicKey().toAddress()
|
|
75
|
-
'1HqfEfHNF9ji9p3AEC66mj8fhGA7sy2WYT'
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
This provides privacy for Alice and Bob, even if eeryone in the world knows Alice and Bob's master public keys.
|
|
79
|
-
|
|
80
|
-
## Going Further: Public Derivation
|
|
81
|
-
|
|
82
|
-
Sometimes, there is a legitimate reason to do "public key derivation" from a key, so that anyone can link a master key to a child key, like in BIP32. To accomplish this, rather than creating a new algorithm, we just use a private key that everyone already knows: the number `1`.
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
alicePub.deriveChild(new PrivateKey(1), '1').toString()
|
|
86
|
-
'0391ff4958a6629be3176330bed0efd99d860f2b7630c21b2e33a42f3cd1740544'
|
|
87
|
-
alicePub.deriveChild(new PrivateKey(1), '2').toString()
|
|
88
|
-
'022ea65d6d66754dc7d94e197dab31a4a8854cdb28d57f216e765af7dfddb5322d'
|
|
89
|
-
alicePub.deriveChild(new PrivateKey(1), 'Bitcoin SV').toString()
|
|
90
|
-
'039e9bf79f4cf7accc061d570b1282bd24d2045be44584e8c6744cd3ff42e1758c'
|
|
91
|
-
alicePub.deriveChild(new PrivateKey(1), '2-tempo-1').toString() // BRC-43 :)
|
|
92
|
-
'02fd1e62689b7faaa718fabe9593da718fb2f966a9b391e5c48f25b1f9fbd4e770'
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Because everyone knows the number `1`, everyone can derive Alice's public keys with these invoice numbers. But only Alice can derive the corresponding private keys:
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
alice.deriveChild(new PrivateKey(1).toPublicKey(), '1').toString()
|
|
99
|
-
'5097922263303694608415912843049723146488598519566487750842578489217455687866'
|
|
100
|
-
alice.deriveChild(new PrivateKey(1).toPublicKey(), '2').toString()
|
|
101
|
-
'30335387255051916743526252561695774618041865683431443265879198629898915116869'
|
|
102
|
-
alice.deriveChild(new PrivateKey(1).toPublicKey(), 'Bitcoin SV').toString()
|
|
103
|
-
'78577766545688955856149390014909118010252835780550951683873199149559002824861'
|
|
104
|
-
alice.deriveChild(new PrivateKey(1).toPublicKey(), '2-tempo-1').toString() // BRC-43 :)
|
|
105
|
-
'85481526811941870977151386860102277207605069038305420293413543998866547111586'
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
The type-42 system enables both public and private key derivation, all while providing a more flexible and open-ended invoice numbering scheme than BIP32.
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Example: Creating a Transaction from UTXOs
|
|
2
|
-
|
|
3
|
-
This guide shows how to create a transaction from UTXOs fetched from WhatsOnChain.
|
|
4
|
-
It also shows how to set a fixed fee and build OP_RETURN outputs.
|
|
5
|
-
|
|
6
|
-
## The UTXOs format
|
|
7
|
-
|
|
8
|
-
Although its not a standard, UTXOs can be fetched from apis like [WhatsOnChain](https://whatsonchain.com/) in a similar format.
|
|
9
|
-
|
|
10
|
-
```typescript
|
|
11
|
-
/** Utxos from whatsonchain api*/
|
|
12
|
-
const utxos = [
|
|
13
|
-
{
|
|
14
|
-
height: 1600000,
|
|
15
|
-
tx_pos: 0,
|
|
16
|
-
tx_hash: '672dd6a93fa5d7ba6794e0bdf8b479440b95a55ec10ad3d9e03585ecb5628d8f',
|
|
17
|
-
value: 10000
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
height: 1600000,
|
|
21
|
-
tx_pos: 0,
|
|
22
|
-
tx_hash: 'f33505acf37a7726cc37d391bc6f889b8684ac2a2d581c4be2a4b1c8b46609bb',
|
|
23
|
-
value: 10000
|
|
24
|
-
},
|
|
25
|
-
]
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Building a transaction from UTXOs
|
|
29
|
-
|
|
30
|
-
The first step is to build a transaction and add inputs from the utxos related to the private key `priv`.
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
import { Transaction, PrivateKey, P2PKH, LockingScript, Utils, OP } from '@bsv/sdk'
|
|
34
|
-
|
|
35
|
-
const priv = PrivateKey.fromWif('...')
|
|
36
|
-
const tx = new Transaction()
|
|
37
|
-
|
|
38
|
-
utxos.forEach(utxo => {
|
|
39
|
-
const script = new P2PKH().lock(priv.toPublicKey().toHash())
|
|
40
|
-
tx.addInput({
|
|
41
|
-
sourceTXID: utxo.tx_hash,
|
|
42
|
-
sourceOutputIndex: utxo.tx_pos,
|
|
43
|
-
sourceSatoshis: utxo.value,
|
|
44
|
-
unlockingScriptTemplate: new P2PKH()
|
|
45
|
-
.unlock(priv, 'all', false, utxo.value, script)
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Adding the outputs
|
|
51
|
-
|
|
52
|
-
Next an OP_RETURN output script is added along with a change address to the original `priv`, note that addresses should not be reused as its not a good practice.
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
const data = Utils.toArray('some data')
|
|
56
|
-
const script = [
|
|
57
|
-
{ op: OP.OP_FALSE },
|
|
58
|
-
{ op: OP.OP_RETURN },
|
|
59
|
-
{ op: data.length, data }
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Add the new OP_RETURN script as output
|
|
63
|
-
tx.addOutput({
|
|
64
|
-
lockingScript: new LockingScript(script),
|
|
65
|
-
satoshis: 0
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// Add a change output to the used private key
|
|
69
|
-
tx.addOutput({
|
|
70
|
-
lockingScript: new P2PKH().lock(priv.toAddress()),
|
|
71
|
-
change: true
|
|
72
|
-
})
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Finalize the transaction
|
|
76
|
-
|
|
77
|
-
Last step is to set the transaction fee, sign the transaction and broadcast it.
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
await tx.fee(10) // a fixed fee of 10 sats is used
|
|
81
|
-
await tx.sign()
|
|
82
|
-
await tx.broadcast()
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
That concludes this basic example but common practice to create a transaction from UTXOs. Refer to other examples for instructions on how to calculate fees from transaction size, use different locking script templates, different broadcast services and more.
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# Example: Verifying a BEEF Structure
|
|
2
|
-
|
|
3
|
-
The BSV SDK comes with advanced capabilities around the SPV architecture. In Bitcoin, SPV refers to the process of creating, exchanging, and verifying transactions in a way that anchors them to the blockchain. One of the standard formats for representing the necessary SPV data is known as BEEF (background-evaluated extended format), representing a transaction and its parents together with needed merkle proofs. This example will show you how to verify the legitimacy of a BEEF-formatted SPV structure you've received, checking to ensure the target transaction and its ancestors are well-anchored to the blockchain based on the current chain of block headers. First we'll unpack these concepts, then we'll dive into the code.
|
|
4
|
-
|
|
5
|
-
## Block Headers and Merkle Proofs
|
|
6
|
-
|
|
7
|
-
In Bitcoin, miners create blocks. These blocks comprise merkle trees of the included transactions. The root of the merkle tree, together with other useful information, is collected together into a block header that can be used to verify the block's proof-of-work. This merkle tree structure enables anyone to keep track of the chain of block headers without keeping a copy of every Bitcoin transaction.
|
|
8
|
-
|
|
9
|
-
Merkle proofs are simply a way for someone to prove the existence of a given transaction within a given merkle tree, and by extension, its inclusion by the miners in a particular block of the blockchain. This becomes extremely important and useful when we think about Simplified Payment Verification (SPV).
|
|
10
|
-
|
|
11
|
-
## Simplified Payment Verification (SPV)
|
|
12
|
-
|
|
13
|
-
The process for SPV is detailed in [BRC-67](https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0067.md), but the main idea is that when a sender sends a transaction, they include merkle proofs on all of the input transactions. This allows anyone with a copy of the Bitcoin block headers to check that the input transactions are included in the blockchain. Verifiers then check that all the input and output scripts correctly transfer value from one party to the next, ensuring an unbroken chain of spends. The [BEEF data structure](https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0062.md) provides a compact and efficient way for people to represent the data required to perform SPV.
|
|
14
|
-
|
|
15
|
-
## Verifying a BEEF Structure
|
|
16
|
-
|
|
17
|
-
Now that you have access to a block headers client (either Pulse on a real project or the above code for a toy example), we can proceed to verifying the BEEF structure with the following code:
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
import { Transaction } from '@bsv/sdk'
|
|
21
|
-
|
|
22
|
-
// Replace with the BEEF structure you'd like to check
|
|
23
|
-
const BEEFHex = '0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000'
|
|
24
|
-
|
|
25
|
-
// You can create a Transaction from BEEF hex directly
|
|
26
|
-
const tx = Transaction.fromHexBEEF(BEEFHex)
|
|
27
|
-
|
|
28
|
-
// This ensures the BEEF structure is legitimate
|
|
29
|
-
const verified = await tx.verify()
|
|
30
|
-
|
|
31
|
-
// Print the results
|
|
32
|
-
console.log(verified)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
The above code allows you to ensure that a given BEEF structure is valid according to the rules of SPV.
|
|
36
|
-
|
|
37
|
-
## Chain tracker
|
|
38
|
-
|
|
39
|
-
To verify BEEF structures with the BSV SDK, you'll need to provide a block headers client that, given a merkle root, will indicate to the library whether the merkle root is correct for the block that's in the active chain at the given block height.
|
|
40
|
-
|
|
41
|
-
The TypeScript BSV SDK does provides default implementation of the chain tracker that use What's On Chain API.
|
|
42
|
-
|
|
43
|
-
### What's On Chain configuration
|
|
44
|
-
|
|
45
|
-
#### BSV network
|
|
46
|
-
|
|
47
|
-
The default network for the chain tracker is `main`. You can change it to other network by providing the instance of WhatsOnChain ChainTracker configured for other network to `.verify()` method.
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
tx.verify(new WhatsOnChain())
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
#### Api Key
|
|
54
|
-
|
|
55
|
-
It is possible to use WhatsOnChain ChainTracker with obtained API KEY of [WhatsOnChain](https://docs.taal.com/core-products/whatsonchain).
|
|
56
|
-
To do so, you need to provide to `.verify()` method the custom instance of WhatsOnChain ChainTracker with the API KEY.
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { Transaction, WhatsOnChain } from '@bsv/sdk'
|
|
60
|
-
|
|
61
|
-
tx.verify(new WhatsOnChain('main', {apiKey: 'YOUR_API_KEY'}))
|
|
62
|
-
```
|
|
@@ -1,97 +0,0 @@
|
|
|
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 [block-headers-service](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
|
-
|
|
44
|
-
Given an array of merkle roots and corresponding block heights, we return a boolean indicating whether they're all valid.
|
|
45
|
-
|
|
46
|
-
We can plug in the Block Header Service API with appropriate HTTP handling logic as follows:
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
import {defaultHttpClient} from "@bsv/sdk";
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Represents a Block Headers Client.
|
|
53
|
-
*/
|
|
54
|
-
export default class BlockHeadersClient implements ChainTracker {
|
|
55
|
-
URL: string
|
|
56
|
-
apiKey: string
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Constructs an instance of the Pulse chain tracker.
|
|
60
|
-
*
|
|
61
|
-
* @param {string} URL - The URL endpoint for the Pulse API.
|
|
62
|
-
* @param {string} apiKey - The API key used for authorization with the Pulse API.
|
|
63
|
-
*/
|
|
64
|
-
constructor(URL: string, apiKey: string) {
|
|
65
|
-
this.URL = URL
|
|
66
|
-
this.apiKey = apiKey
|
|
67
|
-
this.httpClient = defaultHttpClient()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Checks a set of merkle roots with corresponding heights.
|
|
72
|
-
*
|
|
73
|
-
* @param root: string - The merkle root to check
|
|
74
|
-
* @param height: number - The corresponding height
|
|
75
|
-
* @returns {Promise<boolean>} A promise that resolves to either a success or failure response (true or false).
|
|
76
|
-
*/
|
|
77
|
-
async isValidRootForHeight(root: string, height: number): Promise<boolean> {
|
|
78
|
-
const response = await httpsClient(`${this.URL}/api/v1/chain/merkleroot/verify`, {
|
|
79
|
-
method: 'POST',
|
|
80
|
-
body: [{merkleRoot: root, blockHeight: height}],
|
|
81
|
-
headers: {
|
|
82
|
-
'Content-Type': 'application/json',
|
|
83
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
if (response.ok) {
|
|
87
|
-
return response.data?.confirmationState === 'CONFIRMED'
|
|
88
|
-
} else {
|
|
89
|
-
throw new Error(`Failed to verify root at height ${height} with response ${response.status}`)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Now, we can use our `BlockHeadersClient` 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).
|
|
96
|
-
|
|
97
|
-
This provides the ability to ensure that a transaction is well-anchored.
|