@exodus/bitcoin-api 2.4.0 → 2.5.0-alpha.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/package.json +3 -3
- package/src/bitcoinjs-lib/ecc/common.js +2 -1
- package/src/bitcoinjs-lib/ecc/desktop.js +3 -1
- package/src/bitcoinjs-lib/ecc/mobile.js +3 -1
- package/src/bitcoinjs-lib/script-classify/index.js +1 -1
- package/src/btc-like-address.js +1 -1
- package/src/btc-like-keys.js +3 -2
- package/src/fee/get-fee-resolver.js +2 -2
- package/src/parse-unsigned-tx.js +1 -1
- package/src/tx-send/index.js +27 -6
- package/src/tx-sign/default-create-tx.js +13 -7
- package/src/tx-sign/taproot.js +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0-alpha.0",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"@exodus/basic-utils": "^2.0.1",
|
|
23
23
|
"@exodus/bip-schnorr": "0.6.6-fork-1",
|
|
24
24
|
"@exodus/bip44-constants": "^195.0.0",
|
|
25
|
-
"@exodus/bitcoinjs-lib": "6.0.2-beta.5",
|
|
26
25
|
"@exodus/models": "^8.10.4",
|
|
27
26
|
"@exodus/secp256k1": "4.0.2-exodus.0",
|
|
28
27
|
"@exodus/simple-retry": "0.0.6",
|
|
29
28
|
"@exodus/timer": "^1.0.0",
|
|
30
29
|
"@noble/secp256k1": "~1.5.3",
|
|
31
30
|
"bech32": "^1.1.3",
|
|
31
|
+
"bitcoinjs-lib": "^6.1.5",
|
|
32
32
|
"coininfo": "5.1.0",
|
|
33
33
|
"delay": "4.0.1",
|
|
34
34
|
"ecpair": "2.0.1",
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"@exodus/bitcoin-meta": "^1.0.1",
|
|
42
42
|
"jest-when": "^3.5.1"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "04641210f5ac2a4b490cf348b2917b28cf463ba2"
|
|
45
45
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import secp256k1 from '@exodus/secp256k1'
|
|
2
|
+
import { toXOnly } from '../../tx-sign/taproot'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Common ecc functions between mobile and desktop. Once mobile accepts @noble/secp256k1, we can unify both
|
|
@@ -24,7 +25,7 @@ export const common = {
|
|
|
24
25
|
const t = secp256k1.publicKeyTweakAdd(toPubKey(p), tweak)
|
|
25
26
|
return {
|
|
26
27
|
parity: t[0] === 0x02 ? 0 : 1,
|
|
27
|
-
xOnlyPubkey: t
|
|
28
|
+
xOnlyPubkey: toXOnly(t),
|
|
28
29
|
}
|
|
29
30
|
} catch (err) {
|
|
30
31
|
return null
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TinySecp256k1Interface } from '
|
|
1
|
+
import { TinySecp256k1Interface, initEccLib } from 'bitcoinjs-lib'
|
|
2
2
|
import { Point, schnorr, sign } from '@noble/secp256k1'
|
|
3
3
|
import { common, toPubKey } from './common'
|
|
4
4
|
|
|
@@ -48,3 +48,5 @@ export const desktopEcc: TinySecp256k1Interface = {
|
|
|
48
48
|
pointCompress: (p: Uint8Array, compressed?: boolean): Uint8Array =>
|
|
49
49
|
Point.fromHex(p).toRawBytes(compressed),
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
initEccLib(desktopEcc)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TinySecp256k1Interface } from '
|
|
1
|
+
import { TinySecp256k1Interface, initEccLib } from 'bitcoinjs-lib'
|
|
2
2
|
import secp256k1 from '@exodus/secp256k1'
|
|
3
3
|
// TODO: temp import until '@noble/secp256k1' can be used
|
|
4
4
|
import { isPoint } from 'tiny-secp256k1'
|
|
@@ -56,3 +56,5 @@ export const mobileEcc: TinySecp256k1Interface = {
|
|
|
56
56
|
pointCompress: (p: Uint8Array, compressed?: boolean): Uint8Array =>
|
|
57
57
|
secp256k1.publicKeyConvert(p, compressed),
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
initEccLib(mobileEcc)
|
package/src/btc-like-address.js
CHANGED
|
@@ -2,7 +2,7 @@ import bs58check from 'bs58check'
|
|
|
2
2
|
import * as bech32 from 'bech32'
|
|
3
3
|
import assert from 'minimalistic-assert'
|
|
4
4
|
import { identity, pickBy } from 'lodash'
|
|
5
|
-
import * as bitcoinjsOriginal from '
|
|
5
|
+
import * as bitcoinjsOriginal from 'bitcoinjs-lib'
|
|
6
6
|
|
|
7
7
|
export const createBtcLikeAddress = ({
|
|
8
8
|
versions,
|
package/src/btc-like-keys.js
CHANGED
|
@@ -3,9 +3,10 @@ import wif from 'wif'
|
|
|
3
3
|
import * as bech32 from 'bech32'
|
|
4
4
|
import assert from 'minimalistic-assert'
|
|
5
5
|
import { identity, pickBy } from 'lodash'
|
|
6
|
-
import * as defaultBitcoinjsLib from '
|
|
6
|
+
import * as defaultBitcoinjsLib from 'bitcoinjs-lib'
|
|
7
7
|
import secp256k1 from 'secp256k1'
|
|
8
8
|
import { hash160 } from './hash-utils'
|
|
9
|
+
import { toXOnly } from './tx-sign/taproot'
|
|
9
10
|
|
|
10
11
|
export const publicKeyToHashFactory = (p2pkh) => (publicKey) => {
|
|
11
12
|
const payload = Buffer.concat([Buffer.from([p2pkh]), hash160(publicKey)])
|
|
@@ -91,7 +92,7 @@ export const createBtcLikeKeys = ({
|
|
|
91
92
|
? (publicKey: Buffer): string => {
|
|
92
93
|
const network = coinInfo.toBitcoinJS()
|
|
93
94
|
return bitcoinjsLib.payments.p2tr(
|
|
94
|
-
{ internalPubkey: publicKey
|
|
95
|
+
{ internalPubkey: toXOnly(publicKey), network },
|
|
95
96
|
{ eccLib: ecc }
|
|
96
97
|
).address
|
|
97
98
|
}
|
|
@@ -31,7 +31,7 @@ export class GetFeeResolver {
|
|
|
31
31
|
assert(!brc20, 'brc20 must not be provided when nft is provided!!!')
|
|
32
32
|
}
|
|
33
33
|
if (brc20) {
|
|
34
|
-
assert(!amount, 'amount must not be provided when brc20 is provided!!!')
|
|
34
|
+
// assert(!amount, 'amount must not be provided when brc20 is provided!!!')
|
|
35
35
|
assert(!isSendAll, 'isSendAll must not be provided when brc20 is provided!!!')
|
|
36
36
|
assert(!nft, 'nft must not be provided when brc20 is provided!!!')
|
|
37
37
|
}
|
|
@@ -43,7 +43,7 @@ export class GetFeeResolver {
|
|
|
43
43
|
accountState,
|
|
44
44
|
txSet,
|
|
45
45
|
feeData,
|
|
46
|
-
amount,
|
|
46
|
+
amount: brc20 ? undefined : amount,
|
|
47
47
|
customFee,
|
|
48
48
|
isSendAll,
|
|
49
49
|
inscriptionIds,
|
package/src/parse-unsigned-tx.js
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'minimalistic-assert'
|
|
|
2
2
|
import BIPPath from 'bip32-path'
|
|
3
3
|
import BN from 'bn.js'
|
|
4
4
|
import lodash from 'lodash'
|
|
5
|
-
import { Transaction as BitcoinTransactionClass } from '
|
|
5
|
+
import { Transaction as BitcoinTransactionClass } from 'bitcoinjs-lib'
|
|
6
6
|
|
|
7
7
|
export const parseUnsignedTxFactory = ({ Transaction = BitcoinTransactionClass } = {}) => async ({
|
|
8
8
|
asset,
|
package/src/tx-send/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils'
|
|
17
17
|
import { getOrdinalsUtxos, getUsableUtxos, getUtxos } from '../utxos-utils'
|
|
18
18
|
|
|
19
|
-
import * as defaultBitcoinjsLib from '
|
|
19
|
+
import * as defaultBitcoinjsLib from 'bitcoinjs-lib'
|
|
20
20
|
|
|
21
21
|
const ASSETS_SUPPORTED_BIP_174 = [
|
|
22
22
|
'bitcoin',
|
|
@@ -132,7 +132,10 @@ export const createAndBroadcastTXFactory = ({
|
|
|
132
132
|
getFeeEstimator,
|
|
133
133
|
getSizeAndChangeScript = getSizeAndChangeScriptFactory(), // for decred customizations
|
|
134
134
|
allowUnconfirmedRbfEnabledUtxos,
|
|
135
|
-
}) => async (
|
|
135
|
+
}) => async (
|
|
136
|
+
{ asset: maybeToken, walletAccount, address, amount: tokenAmount, options },
|
|
137
|
+
{ assetClientInterface }
|
|
138
|
+
) => {
|
|
136
139
|
const {
|
|
137
140
|
multipleAddressesEnabled,
|
|
138
141
|
feePerKB,
|
|
@@ -143,9 +146,21 @@ export const createAndBroadcastTXFactory = ({
|
|
|
143
146
|
bumpTxId,
|
|
144
147
|
isRbfAllowed = true,
|
|
145
148
|
nft,
|
|
146
|
-
|
|
149
|
+
feeOpts,
|
|
147
150
|
} = options
|
|
148
151
|
|
|
152
|
+
const brc20 = options.brc20 || feeOpts?.brc20 // feeOpts is the only way I've found atm to pass brc20 param without changing the tx-send hydra module
|
|
153
|
+
|
|
154
|
+
const asset = maybeToken.baseAsset
|
|
155
|
+
|
|
156
|
+
const isToken = maybeToken.name !== asset.name
|
|
157
|
+
|
|
158
|
+
if (isToken) {
|
|
159
|
+
assert(brc20, 'brc20 is required when sending bitcoin token')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const amount = isToken ? asset.currency.ZERO : tokenAmount
|
|
163
|
+
|
|
149
164
|
const assetName = asset.name
|
|
150
165
|
|
|
151
166
|
const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
|
|
@@ -458,15 +473,21 @@ export const createAndBroadcastTXFactory = ({
|
|
|
458
473
|
? replaceTx.data.sent.concat([{ address: displayReceiveAddress, amount }])
|
|
459
474
|
: [{ address: displayReceiveAddress, amount }]
|
|
460
475
|
|
|
476
|
+
const coinAmount = selfSend
|
|
477
|
+
? maybeToken.currency.ZERO
|
|
478
|
+
: isToken
|
|
479
|
+
? tokenAmount.abs().negate()
|
|
480
|
+
: totalAmount.abs().negate()
|
|
481
|
+
|
|
461
482
|
await assetClientInterface.updateTxLogAndNotify({
|
|
462
|
-
assetName,
|
|
483
|
+
assetName: maybeToken.name,
|
|
463
484
|
walletAccount,
|
|
464
485
|
txs: [
|
|
465
486
|
{
|
|
466
487
|
txId,
|
|
467
488
|
confirmations: 0,
|
|
468
|
-
coinAmount
|
|
469
|
-
coinName:
|
|
489
|
+
coinAmount,
|
|
490
|
+
coinName: maybeToken.name,
|
|
470
491
|
feeAmount: fee,
|
|
471
492
|
feeCoinName: assetName,
|
|
472
493
|
selfSend,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
2
|
import lodash from 'lodash'
|
|
3
3
|
import ECPairFactory from 'ecpair'
|
|
4
|
-
import { payments, Psbt, Transaction } from '
|
|
4
|
+
import { payments, Psbt, Transaction } from 'bitcoinjs-lib'
|
|
5
5
|
import { getOwnProperty } from '@exodus/basic-utils'
|
|
6
6
|
|
|
7
7
|
import secp256k1 from 'secp256k1'
|
|
8
8
|
|
|
9
|
-
import { toAsyncSigner, tweakSigner } from './taproot'
|
|
9
|
+
import { toAsyncSigner, toXOnly, tweakSigner } from './taproot'
|
|
10
10
|
|
|
11
11
|
let ECPair
|
|
12
12
|
|
|
@@ -63,7 +63,8 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
|
|
|
63
63
|
const privateKey = getOwnProperty(privateKeysAddressMap, address, 'string')
|
|
64
64
|
assert(privateKey, `there is no private key for address ${address}`)
|
|
65
65
|
const key = ECPair.fromWIF(privateKey, networkInfo)
|
|
66
|
-
|
|
66
|
+
const publicKey = secp256k1.publicKeyCreate(key.privateKey, true)
|
|
67
|
+
return { key, purpose, publicKey }
|
|
67
68
|
}
|
|
68
69
|
const path = addressPathsMap[address]
|
|
69
70
|
assert(hdkeys, 'hdkeys must be provided')
|
|
@@ -72,18 +73,24 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
|
|
|
72
73
|
assert(hdkey, `hdkey for purpose for ${purpose} and address ${address} could not be resolved`)
|
|
73
74
|
const derivedhdkey = hdkey.derive(path)
|
|
74
75
|
const privateEncoded = keys.encodePrivate(derivedhdkey.privateKey)
|
|
75
|
-
|
|
76
|
+
const key = ECPair.fromWIF(privateEncoded, networkInfo)
|
|
77
|
+
const publicKey = derivedhdkey.publicKey
|
|
78
|
+
return { key, publicKey, purpose }
|
|
76
79
|
})
|
|
77
80
|
|
|
78
81
|
// Fill tx
|
|
79
82
|
for (const { txId, vout, address, value, script, sequence } of inputs) {
|
|
80
|
-
const { purpose } = getKeyAndPurpose(address)
|
|
83
|
+
const { purpose, publicKey } = getKeyAndPurpose(address)
|
|
84
|
+
|
|
81
85
|
const isSegwitAddress = purpose === 84
|
|
82
86
|
const isTaprootAddress = purpose === 86
|
|
83
87
|
const txIn = { hash: txId, index: vout, sequence }
|
|
84
88
|
if (isSegwitAddress || isTaprootAddress) {
|
|
85
89
|
// witness outputs only require the value and the script, not the full transaction
|
|
86
90
|
txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
|
|
91
|
+
if (isTaprootAddress) {
|
|
92
|
+
txIn.tapInternalKey = toXOnly(publicKey)
|
|
93
|
+
}
|
|
87
94
|
} else {
|
|
88
95
|
const rawTx = (rawTxs || []).find((t) => t.txId === txId)
|
|
89
96
|
// non-witness outptus require the full transaction
|
|
@@ -109,14 +116,13 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
|
|
|
109
116
|
// so signing is only done AFTER all inputs have been updated
|
|
110
117
|
for (let index = 0; index < inputs.length; index++) {
|
|
111
118
|
const { address } = inputs[index]
|
|
112
|
-
const { key, purpose } = getKeyAndPurpose(address)
|
|
119
|
+
const { key, purpose, publicKey } = getKeyAndPurpose(address)
|
|
113
120
|
|
|
114
121
|
if (purpose === 49) {
|
|
115
122
|
// If spending from a P2SH address, we assume the address is P2SH wrapping
|
|
116
123
|
// P2WPKH. Exodus doesn't use P2SH addresses so we should only ever be
|
|
117
124
|
// signing a P2SH input if we are importing a private key
|
|
118
125
|
// BIP143: As a default policy, only compressed public keys are accepted in P2WPKH and P2WSH
|
|
119
|
-
const publicKey = secp256k1.publicKeyCreate(key.privateKey, true)
|
|
120
126
|
const p2wpkh = payments.p2wpkh({ pubkey: publicKey })
|
|
121
127
|
const p2sh = payments.p2sh({ redeem: p2wpkh })
|
|
122
128
|
psbt.updateInput(index, {
|
package/src/tx-sign/taproot.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { crypto } from '
|
|
1
|
+
import { crypto } from 'bitcoinjs-lib'
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
import { getSchnorrEntropy } from './default-entropy'
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ export function tweakSigner({ signer, ECPair, ecc, tweakHash, network }) {
|
|
|
16
16
|
|
|
17
17
|
const tweakedPrivateKey = ecc.privateAdd(
|
|
18
18
|
privateKey,
|
|
19
|
-
tapTweakHash(signer.publicKey
|
|
19
|
+
tapTweakHash(toXOnly(signer.publicKey), tweakHash)
|
|
20
20
|
)
|
|
21
21
|
if (!tweakedPrivateKey) {
|
|
22
22
|
throw new Error('Invalid tweaked private key!')
|
|
@@ -48,3 +48,7 @@ export function toAsyncSigner({ keyPair, ecc }) {
|
|
|
48
48
|
}
|
|
49
49
|
return keyPair
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
export const toXOnly = (publicKey) => {
|
|
53
|
+
return publicKey.slice(1, 33)
|
|
54
|
+
}
|