dcr-ts 0.2.0
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/CHANGELOG.md +563 -0
- package/LICENSE +15 -0
- package/README.md +259 -0
- package/SECURITY.md +106 -0
- package/dist/index.cjs +2424 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +955 -0
- package/dist/index.d.ts +955 -0
- package/dist/index.js +2328 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# dcr-ts
|
|
2
|
+
|
|
3
|
+
Decred (DCR) primitives for TypeScript: **BLAKE-256**, addresses, WIF, **BIP32
|
|
4
|
+
HD keys** with Decred serialization, BIP39 mnemonics, the **transaction wire
|
|
5
|
+
format**, the **Decred signature hash**, and **low-S ECDSA** P2PKH signing.
|
|
6
|
+
|
|
7
|
+
Every consensus-critical byte format is verified byte-for-byte against
|
|
8
|
+
[dcrd](https://github.com/decred/dcrd) — the vectors in
|
|
9
|
+
[`test/fixtures/dcrd-vectors.json`](test/fixtures/dcrd-vectors.json) are
|
|
10
|
+
generated by a [Go program](vectorgen) that imports dcrd directly, so this
|
|
11
|
+
library's output is pinned to the reference implementation rather than to a
|
|
12
|
+
second hand-rolled one.
|
|
13
|
+
|
|
14
|
+
Built from scratch using dcrd as the specification. ISC licensed.
|
|
15
|
+
|
|
16
|
+
> **Not audited.** dcr-ts has had no independent security audit. Byte formats are
|
|
17
|
+
> pinned to dcrd; that says nothing about memory handling, timing or logic bugs.
|
|
18
|
+
> It also cannot erase secrets from memory, offers no constant-time guarantees, and
|
|
19
|
+
> does no transaction-policy validation. Read [SECURITY.md](SECURITY.md) before
|
|
20
|
+
> trusting it with funds.
|
|
21
|
+
|
|
22
|
+
## Design
|
|
23
|
+
|
|
24
|
+
The hard rule mirrors the Rust sibling [`dcr-rs`](https://github.com/jzbz/dcr-rs):
|
|
25
|
+
**hand-roll nothing that touches elliptic-curve math or standard KDFs.** Those
|
|
26
|
+
come from the audited [`@noble`](https://github.com/paulmillr/noble-curves) /
|
|
27
|
+
[`@scure`](https://github.com/paulmillr/scure-bip39) packages. This library owns
|
|
28
|
+
only the Decred-specific glue:
|
|
29
|
+
|
|
30
|
+
- **BLAKE-256** — the 14-round SHA-3 finalist Decred uses for *everything*
|
|
31
|
+
(txids, sighashes, address hashes, base58 checksums). This is **not** BLAKE2 or
|
|
32
|
+
BLAKE3; it is implemented here from the specification and pinned by dcrd
|
|
33
|
+
vectors.
|
|
34
|
+
- **base58check** with the double-BLAKE-256 checksum, and the single-BLAKE-256
|
|
35
|
+
checksum quirk used by WIF.
|
|
36
|
+
- **Addresses** — P2PKH (secp256k1 ECDSA, Ed25519, Schnorr), P2SH, and the
|
|
37
|
+
full-pubkey address, for mainnet, testnet3, simnet and regnet, with
|
|
38
|
+
decode/classify and the correct `OP_CHECKSIGALT` payment scripts for the
|
|
39
|
+
alternative signature suites.
|
|
40
|
+
- **HD keys** — BIP32 with Decred's `dprv`/`dpub` (and `tprv`/`sprv`/`rprv`…)
|
|
41
|
+
version bytes and base58check checksum; private (signer) and public
|
|
42
|
+
(watch-only) derivation. Hardened derivation follows **Decred's variation on
|
|
43
|
+
BIP32, not BIP32 itself** — see below.
|
|
44
|
+
- **Transactions** — the dcrd `MsgTx` wire format (prefix ‖ witness), byte-exact
|
|
45
|
+
serialize/parse, and all three txid variants.
|
|
46
|
+
- **Signing** — the Decred signature hash (not Bitcoin's BIP143) and
|
|
47
|
+
RFC 6979 / low-S ECDSA signature scripts for P2PKH inputs.
|
|
48
|
+
|
|
49
|
+
Out of scope: networking/RPC, staking/tickets, mixing, and transaction-building
|
|
50
|
+
policy (coin selection, fees).
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install dcr-ts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Ships ESM and CommonJS with type declarations. Node ≥ 18.
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### Hashing and addresses
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { blake256, addressFromPubKey, decodeAddress, mainnet } from "dcr-ts";
|
|
66
|
+
|
|
67
|
+
blake256(new Uint8Array()); // 716f6e86… (BLAKE-256, not BLAKE2/3)
|
|
68
|
+
|
|
69
|
+
const addr = addressFromPubKey(compressedPubKey, mainnet); // "Ds…"
|
|
70
|
+
const { kind, hash, network } = decodeAddress(addr); // "pubkeyhash-ecdsa"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### HD keys from a mnemonic
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { mnemonicToMasterKey, mainnet } from "dcr-ts";
|
|
77
|
+
|
|
78
|
+
const master = mnemonicToMasterKey(mnemonic, mainnet);
|
|
79
|
+
const key = master.derivePath("m/44'/42'/0'/0/0");
|
|
80
|
+
key.toString(); // "dprv…"
|
|
81
|
+
key.address(); // "Ds…"
|
|
82
|
+
|
|
83
|
+
// Watch-only: neuter an account key and derive receive addresses publicly.
|
|
84
|
+
const xpub = master.derivePath("m/44'/42'/0'").neuter();
|
|
85
|
+
xpub.derive(0).derive(0).address();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Build and sign a transaction
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import {
|
|
92
|
+
Transaction,
|
|
93
|
+
outPointFromTxid,
|
|
94
|
+
addressToScript,
|
|
95
|
+
signP2PKHInput,
|
|
96
|
+
decodeWif,
|
|
97
|
+
mainnet,
|
|
98
|
+
} from "dcr-ts";
|
|
99
|
+
|
|
100
|
+
const { privateKey } = decodeWif(wif);
|
|
101
|
+
// The network is required: a payment script commits only to the 20-byte hash, so
|
|
102
|
+
// without it a pasted testnet address would silently pay whoever controls that
|
|
103
|
+
// hash on mainnet.
|
|
104
|
+
const prevScript = addressToScript(myAddress, mainnet);
|
|
105
|
+
|
|
106
|
+
const tx = new Transaction();
|
|
107
|
+
tx.addInput(outPointFromTxid(prevTxid, vout), { valueIn: 200_000_000n });
|
|
108
|
+
tx.addOutput(199_990_000n, addressToScript(destinationAddress, mainnet));
|
|
109
|
+
|
|
110
|
+
signP2PKHInput(tx, 0, prevScript, privateKey); // SigHashAll, low-S, RFC 6979
|
|
111
|
+
tx.serialize(); // Uint8Array ready for the wire
|
|
112
|
+
tx.txid(); // reversed-hex display id
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`Transaction` is a mutable builder and does no policy validation: it will not
|
|
116
|
+
check fees, dust, amount bounds or that your inputs cover your outputs. It copies
|
|
117
|
+
the buffers you hand it, so mutating your own scripts afterwards cannot rewrite a
|
|
118
|
+
transaction you have already signed.
|
|
119
|
+
|
|
120
|
+
### Amounts
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { dcrToAtoms, atomsToDcr } from "dcr-ts";
|
|
124
|
+
|
|
125
|
+
dcrToAtoms("1.5"); // 150000000n
|
|
126
|
+
atomsToDcr(150_000_000n); // "1.50000000"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Signing several inputs
|
|
130
|
+
|
|
131
|
+
`calcSignatureHash` re-serializes and re-hashes the whole transaction prefix per
|
|
132
|
+
call, so signing N inputs one at a time is O(N²). Under `SigHashAll` the prefix
|
|
133
|
+
half does not depend on which input is being signed, so `signP2PKHInputs`
|
|
134
|
+
computes it once — the same thing dcrd's `cachedPrefix` argument is for:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { signP2PKHInputs } from "dcr-ts";
|
|
138
|
+
|
|
139
|
+
signP2PKHInputs(tx, [
|
|
140
|
+
{ idx: 0, subScript: prevScript0, privateKey: key0 },
|
|
141
|
+
{ idx: 1, subScript: prevScript1, privateKey: key1 },
|
|
142
|
+
]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Byte-identical to calling `signP2PKHInput` per input. On the hashing alone this
|
|
146
|
+
is 12–26x for 50–1000 inputs; end to end the win is smaller (1.7x at 250 inputs)
|
|
147
|
+
because ECDSA dominates. `calcSignatureHash` also takes an optional
|
|
148
|
+
`cachedPrefix` from `sigHashPrefixAll(tx)` if you are building signature scripts
|
|
149
|
+
yourself.
|
|
150
|
+
|
|
151
|
+
## Hardened derivation is not plain BIP32
|
|
152
|
+
|
|
153
|
+
Decred deviates from BIP32 in the hardened child function, and the difference is
|
|
154
|
+
load-bearing. dcrd's `hdkeychain` strips leading zero bytes from a derived
|
|
155
|
+
private key and carries the shortened string into the next hardened HMAC:
|
|
156
|
+
|
|
157
|
+
> Note that per [BIP32] this should be the fully zero-padded 32-bytes, however,
|
|
158
|
+
> the Decred variation strips leading zeros for legacy reasons and changing it
|
|
159
|
+
> now would break derivation for a lot of Decred wallets that rely on this
|
|
160
|
+
> behavior.
|
|
161
|
+
|
|
162
|
+
So for a parent scalar with a leading zero byte the hardened HMAC input is
|
|
163
|
+
`0x00 ‖ key31 ‖ 0x00 ‖ ser32(i)` rather than BIP32's
|
|
164
|
+
`0x00 ‖ 0x00 ‖ key31 ‖ ser32(i)` — the same length, different bytes, and every
|
|
165
|
+
descendant diverges. Roughly **1 seed in 128** is affected on a BIP44 path — two hardened levels
|
|
166
|
+
below the master, each with a 1/256 chance of a leading zero byte. Measured
|
|
167
|
+
0.8–0.9% over 20,000 seeds.
|
|
168
|
+
|
|
169
|
+
dcrd exposes both variants and dcrwallet uses the legacy one for the entire
|
|
170
|
+
wallet path, so this library mirrors that:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
key.derive(0); // Decred variant — dcrd Child, what wallets use
|
|
174
|
+
key.derivePath("m/44'/42'/0'/0/0");
|
|
175
|
+
|
|
176
|
+
key.deriveBip32Std(0); // strict BIP32 — dcrd ChildBIP32Std
|
|
177
|
+
key.derivePathBip32Std("m/44'/42'/0'/0/0");
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Use the strict form only when strict BIP32 is genuinely what you want. Anything
|
|
181
|
+
that has to agree with a Decrediton or dcrwallet seed must not: getting it wrong
|
|
182
|
+
is silent, showing the user a different, empty wallet with coins sent to its
|
|
183
|
+
addresses invisible to every other Decred wallet holding the same phrase.
|
|
184
|
+
|
|
185
|
+
Two consequences worth knowing:
|
|
186
|
+
|
|
187
|
+
- **Public (non-hardened) derivation is unaffected.** There is no private key to
|
|
188
|
+
strip, and a stripped scalar has the same value and therefore the same public
|
|
189
|
+
key, so an account `dpub` and every address below it agree between variants.
|
|
190
|
+
- **The stripped state does not survive serialization.** dcrd pads the scalar
|
|
191
|
+
back out in the extended-key string, so a key round-tripped through `dprv`
|
|
192
|
+
derives strictly from then on — in dcrd too, which this mirrors. Only hardened
|
|
193
|
+
steps are affected, and in BIP44 the deepest hardened level is the account key,
|
|
194
|
+
so it rarely shows up in practice.
|
|
195
|
+
|
|
196
|
+
Both variants are pinned against dcrd-generated vectors from a seed chosen to
|
|
197
|
+
make them disagree (`hd.leadingZero` in the fixture).
|
|
198
|
+
|
|
199
|
+
## Where this deliberately does not match dcrd
|
|
200
|
+
|
|
201
|
+
Byte-exactness is the goal everywhere it is achievable, and every serialization
|
|
202
|
+
this library produces is pinned against dcrd-generated vectors. But byte formats
|
|
203
|
+
are not the whole contract: two implementations can agree on every byte they
|
|
204
|
+
*emit* and still disagree on what they *accept*. Four accept/reject divergences
|
|
205
|
+
are deliberate. Each one fails closed — this library rejects something dcrd
|
|
206
|
+
takes — so "accepted by dcr-ts" implies "accepted by dcrd", never the reverse.
|
|
207
|
+
|
|
208
|
+
- **An extended key whose version and key type disagree is refused.**
|
|
209
|
+
`NewKeyFromString` decides private-vs-public from the key-data byte and treats
|
|
210
|
+
the version bytes only as a network tag, so a string beginning `dpub` that
|
|
211
|
+
wraps `0x00 ‖ privkey32` parses there as a *private* key — and re-serializes as
|
|
212
|
+
`dprv`, since dcrd re-attaches the version matching the key it ended up with.
|
|
213
|
+
`ExtendedKey.fromString` takes the type from the version and throws
|
|
214
|
+
`invalid-public-key` (or `invalid-private-key` the other way). No honest encoder
|
|
215
|
+
emits such a string, dcrd's own `String()` included, so nothing round-trips
|
|
216
|
+
differently. The point is that a `dpub` prefix and `isPrivate === false` can
|
|
217
|
+
never disagree, which matters because "it starts with dpub, so it is safe to
|
|
218
|
+
paste here" is a real pattern.
|
|
219
|
+
- **A WIF with an unknown signature-suite byte is refused.** dcrd's `DecodeWIF`
|
|
220
|
+
switches on that byte with no default arm, so an unrecognised suite yields a
|
|
221
|
+
`WIF` holding a **nil** private key, with the scheme silently defaulted to
|
|
222
|
+
ECDSA and no error. Its own `String()` on that struct is not a WIF. There is
|
|
223
|
+
nothing there to be compatible with.
|
|
224
|
+
- **The signing entry points reject an unusable private key.** dcrd's
|
|
225
|
+
`secp256k1.PrivKeyFromBytes` cannot fail: it reduces mod n and left-pads a
|
|
226
|
+
short slice, so a zero key signs under an all-zero-X public key and a 31-byte
|
|
227
|
+
key is silently padded and signed. `signHash` and friends throw
|
|
228
|
+
`invalid-private-key` or `bad-length` instead.
|
|
229
|
+
- **`Transaction.fromBytes` applies no input/output count caps.** This one is the
|
|
230
|
+
exception to the pattern above: it is *more* permissive than dcrd, which
|
|
231
|
+
rejects counts over `maxTxInPerMessage` (780336) or `maxTxOutPerMessage`
|
|
232
|
+
(3728271). Those bounds exist because dcrd decodes from an `io.Reader` of
|
|
233
|
+
unknown length and sizes `make([]TxIn, count)` from the count before reading
|
|
234
|
+
anything; here the argument is a `Uint8Array` whose length is already the bound,
|
|
235
|
+
and nothing is allocated from a declared count. The only blobs that parse here
|
|
236
|
+
and not there are ~43 MiB or larger, which is over `MaxMessagePayload` and 115x
|
|
237
|
+
mainnet's `MaxTxSize` — neither relayable nor valid. See `fromBytes` for the
|
|
238
|
+
caller-side sizing advice that does matter.
|
|
239
|
+
|
|
240
|
+
One more divergence is worth naming because it is not a rejection at all:
|
|
241
|
+
`ExtendedKey.fromString` takes **no network** and recognises all four, reporting
|
|
242
|
+
which it found on `.network`. dcrd's `NewKeyFromString` takes `NetworkParams` and
|
|
243
|
+
returns `ErrWrongNetwork` for any other network's version. A caller that wants
|
|
244
|
+
dcrd's answer compares `.network` itself.
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npm install
|
|
250
|
+
npm test # vitest, all vectors checked against dcrd
|
|
251
|
+
npm run typecheck # tsc --strict
|
|
252
|
+
npm run build # tsup → dist (esm + cjs + d.ts)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
To regenerate the dcrd vectors (requires Go), see [`vectorgen`](vectorgen).
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
ISC
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## This library has not been audited
|
|
4
|
+
|
|
5
|
+
dcr-ts has had no independent security audit. It is a from-specification
|
|
6
|
+
implementation of Decred's byte formats, checked against dcrd-generated test
|
|
7
|
+
vectors. That pins the formats; it does not make the library safe to hold
|
|
8
|
+
significant value with. Read the code before you trust it with money.
|
|
9
|
+
|
|
10
|
+
What the "byte-exact with dcrd" claim in the README does and does not mean:
|
|
11
|
+
|
|
12
|
+
- **Does mean:** every consensus-critical byte format the library implements is
|
|
13
|
+
compared against output generated by a Go program that imports dcrd itself, and
|
|
14
|
+
CI fails if the vectors drift. The covered surface is enumerated in
|
|
15
|
+
[`vectorgen/main.go`](vectorgen/main.go).
|
|
16
|
+
- **Does not mean:** the library is free of memory-handling, timing, validation or
|
|
17
|
+
logic bugs, that the covered surface is complete, or that anyone has attacked it.
|
|
18
|
+
|
|
19
|
+
## Reporting a vulnerability
|
|
20
|
+
|
|
21
|
+
Email **jz@jz.bz**. Please do not open a public issue for anything that could
|
|
22
|
+
affect funds.
|
|
23
|
+
|
|
24
|
+
There is no bounty and no formal SLA. Expect an acknowledgement within a few days.
|
|
25
|
+
|
|
26
|
+
## Threat model and known limits
|
|
27
|
+
|
|
28
|
+
Worth understanding before building on this.
|
|
29
|
+
|
|
30
|
+
### Secret material is not protected in memory
|
|
31
|
+
|
|
32
|
+
JavaScript cannot reliably erase a secret. There is no guaranteed-wipe primitive,
|
|
33
|
+
strings are immutable and interned, and the garbage collector copies objects freely,
|
|
34
|
+
so a private key handed to this library may persist in memory in places nothing can
|
|
35
|
+
reach to clear.
|
|
36
|
+
|
|
37
|
+
Concretely, these hand back live secret material and place no constraints on what
|
|
38
|
+
you do with it:
|
|
39
|
+
|
|
40
|
+
- `decodeWif().privateKey`
|
|
41
|
+
- `ExtendedKey.privateKeyBytes()` and `ExtendedKey.serialize()`
|
|
42
|
+
- `mnemonicToSeed()` and `mnemonicToEntropy()`
|
|
43
|
+
- `ExtendedKey.toString()` for a private key — a base58 string, so it cannot be
|
|
44
|
+
wiped at all
|
|
45
|
+
|
|
46
|
+
dcrd offers `ExtendedKey.Zero()` and the Rust sibling `dcr-rs` zeroizes on drop;
|
|
47
|
+
neither is fully achievable here. Treat process memory as compromised if the
|
|
48
|
+
process is.
|
|
49
|
+
|
|
50
|
+
### No constant-time guarantees
|
|
51
|
+
|
|
52
|
+
Elliptic-curve operations come from [`@noble/curves`](https://github.com/paulmillr/noble-curves),
|
|
53
|
+
which documents its own timing properties. The Decred-specific code in this library
|
|
54
|
+
— base58, byte serialization, BLAKE-256, comparisons — is **not** written to be
|
|
55
|
+
constant-time, and JIT compilation makes such claims hard to support in JavaScript
|
|
56
|
+
regardless. Do not use this where an attacker can measure your timing.
|
|
57
|
+
|
|
58
|
+
### Untrusted input must be size-capped by the caller
|
|
59
|
+
|
|
60
|
+
`Transaction.fromBytes` is linear in the length of the buffer it is handed and
|
|
61
|
+
imposes no limit of its own. It allocates nothing from a declared input or output
|
|
62
|
+
count — an inflated count fails at the first read past the end of the buffer, not
|
|
63
|
+
after reserving memory for it — so the cost is bounded by the bytes you actually
|
|
64
|
+
pass. That still means a large buffer costs proportionally large time and memory.
|
|
65
|
+
|
|
66
|
+
dcrd's own `maxTxInPerMessage` / `maxTxOutPerMessage` caps are not reproduced,
|
|
67
|
+
and would not help if they were: they bound counts, not bytes, and a buffer at
|
|
68
|
+
dcrd's 32 MiB wire maximum holds fewer minimal inputs than the cap allows. Cap
|
|
69
|
+
the size of anything untrusted before parsing it.
|
|
70
|
+
|
|
71
|
+
base58 decoding is quadratic, so the bound matters more there than anywhere
|
|
72
|
+
else, and which functions carry it is worth stating precisely:
|
|
73
|
+
|
|
74
|
+
- **Bounded for you.** `decodeAddress`, `isValidAddress`, `addressToScript`,
|
|
75
|
+
`decodeWif` and `ExtendedKey.fromString` each check a length before decoding —
|
|
76
|
+
`MAX_ADDRESS_LENGTH`, `MAX_WIF_LENGTH` and `MAX_EXTENDED_KEY_LENGTH`, which are
|
|
77
|
+
dcrd's own bounds. An address or key arriving from outside should reach one of
|
|
78
|
+
these.
|
|
79
|
+
- **Deliberately not bounded.** `base58Decode` and `checkDecode` are exported as
|
|
80
|
+
general-purpose primitives and take no cap, because any cap on them would have
|
|
81
|
+
to assume a format they know nothing about. A 128 KB argument blocks the event
|
|
82
|
+
loop for several seconds. If you call either on input you did not construct,
|
|
83
|
+
cap it yourself first: `maxBase58Length(n)` gives the ceiling for an `n`-byte
|
|
84
|
+
payload, and it is what the bounded decoders above use.
|
|
85
|
+
|
|
86
|
+
### Out of scope
|
|
87
|
+
|
|
88
|
+
The library does not implement, and will not protect you from getting wrong:
|
|
89
|
+
|
|
90
|
+
- Script execution or transaction validation. `Transaction` is a mutable builder
|
|
91
|
+
that performs no policy checks: not fees, dust, amount bounds, nor whether inputs
|
|
92
|
+
cover outputs. It will happily build and sign a transaction the network rejects.
|
|
93
|
+
- Signing for the Ed25519 or Schnorr signature suites. Addresses for them are
|
|
94
|
+
recognised; only secp256k1 ECDSA can be signed.
|
|
95
|
+
- Staking, tickets, mixing, networking and RPC.
|
|
96
|
+
- Coin selection, change handling and fee estimation.
|
|
97
|
+
|
|
98
|
+
### Randomness
|
|
99
|
+
|
|
100
|
+
`generateMnemonic` draws from `@scure/bip39`, which uses the platform CSPRNG. The
|
|
101
|
+
library generates no other randomness — ECDSA nonces are deterministic (RFC 6979),
|
|
102
|
+
which is what makes signatures byte-reproducible against dcrd.
|
|
103
|
+
|
|
104
|
+
## Supported versions
|
|
105
|
+
|
|
106
|
+
Pre-1.0: only the latest release gets fixes.
|