@exodus/bitcoin-api 2.26.1 → 2.27.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 +19 -0
- package/package.json +5 -8
- package/src/balances.js +7 -1
- package/src/fee/can-bump-tx.js +2 -2
- package/src/fee/fee-utils.js +2 -1
- package/src/multisig-address.js +1 -1
- package/src/parse-unsigned-tx.js +3 -4
- package/src/tx-sign/create-get-key-and-purpose.js +8 -8
- package/src/tx-sign/create-sign-with-wallet.js +3 -4
- package/src/tx-sign/default-create-tx.js +5 -0
- package/src/tx-sign/default-prepare-for-signing.js +24 -6
- package/src/tx-sign/default-sign-hardware.js +1 -1
- package/src/tx-sign/taproot.js +43 -55
- package/src/utxos-utils.js +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.27.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.26.1...@exodus/bitcoin-api@2.27.0) (2024-10-17)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **bitcoin-api:** remove bn.js dep ([#4150](https://github.com/ExodusMovement/assets/issues/4150)) ([70082ff](https://github.com/ExodusMovement/assets/commit/70082ffd5f1ffbd5537c7978f6fa1c4333cb8bc9))
|
|
12
|
+
* **bitcoin-api:** use 'sig' keychain enc ([#4154](https://github.com/ExodusMovement/assets/issues/4154)) ([4c9fe45](https://github.com/ExodusMovement/assets/commit/4c9fe454fdf9a1bda3bd683ccde2359ecdabf584))
|
|
13
|
+
* btc-like unconfirmed received ([#4277](https://github.com/ExodusMovement/assets/issues/4277)) ([70d4b73](https://github.com/ExodusMovement/assets/commit/70d4b73811acd87265ad2c1fcf984afb5a89df73))
|
|
14
|
+
* cleaner dogecoin bitcoinjs-lib ([#4111](https://github.com/ExodusMovement/assets/issues/4111)) ([5f21273](https://github.com/ExodusMovement/assets/commit/5f212734d9ea732fdf74f9affafb10facdc6a828))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* harden derivation path map with null prototype ([#4067](https://github.com/ExodusMovement/assets/issues/4067)) ([6c900cf](https://github.com/ExodusMovement/assets/commit/6c900cf1e285d129f66c4aa98fdeb811d13a23c4))
|
|
20
|
+
* reduce max pubkeys for multisig to 16 ([#4108](https://github.com/ExodusMovement/assets/issues/4108)) ([8c4c33b](https://github.com/ExodusMovement/assets/commit/8c4c33b08c5dc0786d601887e08b413a780b32a7))
|
|
21
|
+
* remove stray direct dependencies ([#4176](https://github.com/ExodusMovement/assets/issues/4176)) ([c4f93fa](https://github.com/ExodusMovement/assets/commit/c4f93fad5a930b40d326d7add1093b2d4f243f2a))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
## [2.26.1](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.26.0...@exodus/bitcoin-api@2.26.1) (2024-09-30)
|
|
7
26
|
|
|
8
27
|
**Note:** Version bump only for package @exodus/bitcoin-api
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.27.0",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -22,21 +22,19 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@exodus/asset-lib": "^5.0.0",
|
|
24
24
|
"@exodus/basic-utils": "^3.0.1",
|
|
25
|
-
"@exodus/bip32": "^3.
|
|
25
|
+
"@exodus/bip32": "^3.3.0",
|
|
26
26
|
"@exodus/bip322-js": "^1.1.0",
|
|
27
27
|
"@exodus/bip44-constants": "^195.0.0",
|
|
28
28
|
"@exodus/bitcoin-lib": "^2.4.2",
|
|
29
29
|
"@exodus/bitcoinjs": "^1.1.0",
|
|
30
|
-
"@exodus/crypto": "^1.0.0-rc.
|
|
30
|
+
"@exodus/crypto": "^1.0.0-rc.13",
|
|
31
31
|
"@exodus/currency": "^5.0.2",
|
|
32
|
-
"@exodus/key-identifier": "^1.
|
|
32
|
+
"@exodus/key-identifier": "^1.3.0",
|
|
33
33
|
"@exodus/models": "^12.0.1",
|
|
34
34
|
"@exodus/simple-retry": "^0.0.6",
|
|
35
35
|
"bech32": "^1.1.3",
|
|
36
36
|
"bip32-path": "^0.4.2",
|
|
37
|
-
"bn.js": "^4.12.0",
|
|
38
37
|
"bs58check": "^2.1.2",
|
|
39
|
-
"coininfo": "^5.1.0",
|
|
40
38
|
"delay": "^4.0.1",
|
|
41
39
|
"lodash": "^4.17.21",
|
|
42
40
|
"minimalistic-assert": "^1.0.1",
|
|
@@ -47,7 +45,6 @@
|
|
|
47
45
|
"wif": "^2.0.6"
|
|
48
46
|
},
|
|
49
47
|
"devDependencies": {
|
|
50
|
-
"@exodus/assets-testing": "^1.0.0",
|
|
51
48
|
"@exodus/bitcoin-meta": "^2.0.0",
|
|
52
49
|
"jest-when": "^3.5.1"
|
|
53
50
|
},
|
|
@@ -59,5 +56,5 @@
|
|
|
59
56
|
"type": "git",
|
|
60
57
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
61
58
|
},
|
|
62
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "3218754fdfad087b6d789eb81f7fe9f21b636fef"
|
|
63
60
|
}
|
package/src/balances.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { getUnconfirmedSentBalance } from '@exodus/asset-lib'
|
|
1
2
|
import assert from 'minimalistic-assert'
|
|
2
3
|
|
|
3
|
-
import { getUtxos } from './utxos-utils.js'
|
|
4
|
+
import { getUnconfirmedUtxos, getUtxos } from './utxos-utils.js'
|
|
4
5
|
|
|
5
6
|
// known issue!! fee data is static here!
|
|
6
7
|
// Hydra's balances's module does not provide it when calling asset.api.getBalances
|
|
@@ -40,9 +41,14 @@ export const getBalancesFactory = ({ feeData, getSpendableBalance, ordinalsEnabl
|
|
|
40
41
|
txSet: txLog,
|
|
41
42
|
feeData,
|
|
42
43
|
})
|
|
44
|
+
|
|
45
|
+
const unconfirmedUtxos = getUnconfirmedUtxos({ utxos })
|
|
46
|
+
const unconfirmedSent = getUnconfirmedSentBalance({ asset, txLog })
|
|
43
47
|
return {
|
|
44
48
|
total: balance,
|
|
45
49
|
spendable: spendableBalance,
|
|
50
|
+
unconfirmedReceived: unconfirmedUtxos.value,
|
|
51
|
+
unconfirmedSent,
|
|
46
52
|
// legacy
|
|
47
53
|
balance,
|
|
48
54
|
spendableBalance,
|
package/src/fee/can-bump-tx.js
CHANGED
|
@@ -3,7 +3,7 @@ import assert from 'minimalistic-assert'
|
|
|
3
3
|
|
|
4
4
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils.js'
|
|
5
5
|
import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data.js'
|
|
6
|
-
import { getUsableUtxos, getUtxos } from '../utxos-utils.js'
|
|
6
|
+
import { getUsableUtxos, getUtxos, isUtxoConfirmed } from '../utxos-utils.js'
|
|
7
7
|
import { selectUtxos } from './utxo-selector.js'
|
|
8
8
|
|
|
9
9
|
export const ASSET_NAMES = ['bitcoin', 'bitcoinregtest', 'bitcointestnet']
|
|
@@ -76,7 +76,7 @@ const _canBumpTx = ({
|
|
|
76
76
|
// Can't bump a non-rbf tx with no change
|
|
77
77
|
if (!bumpTx && changeUtxos.size === 0) return { errorMessage: 'no change' }
|
|
78
78
|
// Can't bump a confirmed tx
|
|
79
|
-
if (!bumpTx && changeUtxos.toArray().every(
|
|
79
|
+
if (!bumpTx && changeUtxos.toArray().every(isUtxoConfirmed))
|
|
80
80
|
return { errorMessage: 'already confirmed' }
|
|
81
81
|
// Can't bump an rbf tx if change is already spent
|
|
82
82
|
if (bumpTx && bumpTx.data.changeAddress && changeUtxos.size === 0)
|
package/src/fee/fee-utils.js
CHANGED
|
@@ -3,6 +3,7 @@ import { UtxoCollection } from '@exodus/models'
|
|
|
3
3
|
import assert from 'minimalistic-assert'
|
|
4
4
|
|
|
5
5
|
import { resolveExtraFeeOfTx } from '../unconfirmed-ancestor-data.js'
|
|
6
|
+
import { isUtxoConfirmed } from '../utxos-utils.js'
|
|
6
7
|
|
|
7
8
|
export const isHex = (s) => typeof s === 'string' && /[\da-f]*/.test(s.toLowerCase())
|
|
8
9
|
|
|
@@ -14,7 +15,7 @@ export function getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor })
|
|
|
14
15
|
(inputs instanceof UtxoCollection || !Array.isArray(inputs))
|
|
15
16
|
) {
|
|
16
17
|
const feeRate = feePerKB.toBaseNumber() / 1000
|
|
17
|
-
const utxos = [...inputs].filter((
|
|
18
|
+
const utxos = [...inputs].filter((utxo) => !isUtxoConfirmed(utxo))
|
|
18
19
|
const txIds = new Set(utxos.map(({ txId }) => txId))
|
|
19
20
|
for (const txId of txIds) {
|
|
20
21
|
extraFee += resolveExtraFeeOfTx({
|
package/src/multisig-address.js
CHANGED
|
@@ -13,7 +13,7 @@ const DUMMY_TAPROOT_PUBKEY = Buffer.from(
|
|
|
13
13
|
const LEAF_VERSION_TAPSCRIPT = 192
|
|
14
14
|
|
|
15
15
|
// Limit multisig keys to 16 for now
|
|
16
|
-
const MAX_PUBKEYS =
|
|
16
|
+
const MAX_PUBKEYS = 16
|
|
17
17
|
|
|
18
18
|
export const createEncodeMultisigContract =
|
|
19
19
|
({ bitcoinjsLib = defaultBitcoinjsLib, network = bitcoinjsLib.Network.bitcoin, ecc }) =>
|
package/src/parse-unsigned-tx.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Transaction as BitcoinTransactionClass } from '@exodus/bitcoinjs'
|
|
2
2
|
import BIPPath from 'bip32-path'
|
|
3
|
-
import BN from 'bn.js'
|
|
4
3
|
import lodash from 'lodash'
|
|
5
4
|
import assert from 'minimalistic-assert'
|
|
6
5
|
|
|
@@ -10,8 +9,8 @@ export const parseUnsignedTxFactory =
|
|
|
10
9
|
const assetName = asset.name
|
|
11
10
|
|
|
12
11
|
const toNumberUnit = (value) => {
|
|
13
|
-
const
|
|
14
|
-
return asset.currency.baseUnit(
|
|
12
|
+
const parsed = Buffer.isBuffer(value) ? BigInt('0x' + value.reverse().toString('hex')) : value // handle Dogecoin buffer values
|
|
13
|
+
return asset.currency.baseUnit(parsed)
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
const parseAddress = assetName === 'bcash' ? asset.address.toCashAddress : (address) => address
|
|
@@ -33,7 +32,7 @@ export const parseUnsignedTxFactory =
|
|
|
33
32
|
inputs.forEach(({ txId, vout, value }) => {
|
|
34
33
|
const tx = inputTxsById.get(txId)
|
|
35
34
|
const output = tx.outs[vout]
|
|
36
|
-
const expected =
|
|
35
|
+
const expected = output.value
|
|
37
36
|
assert(
|
|
38
37
|
lodash.isEqual(expected, value),
|
|
39
38
|
`${txId} tx input has invalid value. Expected: ${expected} , Actual: ${value}`
|
|
@@ -30,11 +30,11 @@ export const createGetKeyWithMetadata = ({
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
function getPrivateKeyFromMap(privateKeysAddressMap, networkInfo, purpose, address) {
|
|
33
|
-
const
|
|
34
|
-
assert(
|
|
35
|
-
const
|
|
36
|
-
const publicKey = privateKeyToPublicKey({ privateKey
|
|
37
|
-
return {
|
|
33
|
+
const privateWif = getOwnProperty(privateKeysAddressMap, address, 'string')
|
|
34
|
+
assert(privateWif, `there is no private key for address ${address}`)
|
|
35
|
+
const { privateKey } = ECPair.fromWIF(privateWif, networkInfo)
|
|
36
|
+
const publicKey = privateKeyToPublicKey({ privateKey, format: 'buffer' })
|
|
37
|
+
return { privateKey, publicKey, purpose }
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function getPrivateKeyFromHDKeys(hdkeys, addressPathsMap, networkInfo, purpose, address) {
|
|
@@ -44,9 +44,9 @@ function getPrivateKeyFromHDKeys(hdkeys, addressPathsMap, networkInfo, purpose,
|
|
|
44
44
|
const hdkey = hdkeys[purpose]
|
|
45
45
|
assert(hdkey, `hdkey for purpose for ${purpose} and address ${address} could not be resolved`)
|
|
46
46
|
const derivedhdkey = hdkey.derive(path)
|
|
47
|
-
const
|
|
47
|
+
const { privateKey } = ECPair.fromPrivateKey(derivedhdkey.privateKey, { network: networkInfo })
|
|
48
48
|
const publicKey = derivedhdkey.publicKey
|
|
49
|
-
return {
|
|
49
|
+
return { privateKey, publicKey, purpose }
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
async function getPublicKeyFromSigner(signer, addressPathsMap, purpose, address, getKeyIdentifier) {
|
|
@@ -55,5 +55,5 @@ async function getPublicKeyFromSigner(signer, addressPathsMap, purpose, address,
|
|
|
55
55
|
const [chainIndex, addressIndex] = BipPath.fromString(addressPath).toPathArray()
|
|
56
56
|
const keyId = new KeyIdentifier(getKeyIdentifier({ purpose, chainIndex, addressIndex }))
|
|
57
57
|
const publicKey = await signer.getPublicKey({ keyId })
|
|
58
|
-
return {
|
|
58
|
+
return { keyId, publicKey, purpose }
|
|
59
59
|
}
|
|
@@ -11,7 +11,6 @@ export function createSignWithWallet({
|
|
|
11
11
|
privateKeysAddressMap,
|
|
12
12
|
addressPathsMap,
|
|
13
13
|
coinInfo,
|
|
14
|
-
network,
|
|
15
14
|
getKeyIdentifier,
|
|
16
15
|
}) {
|
|
17
16
|
const getKeyWithMetadata = createGetKeyWithMetadata({
|
|
@@ -42,7 +41,7 @@ export function createSignWithWallet({
|
|
|
42
41
|
sigHash === undefined
|
|
43
42
|
? undefined // `SIGHASH_DEFAULT` is a default safe sig hash, always allow it.
|
|
44
43
|
: [sigHash, Transaction.SIGHASH_ALL]
|
|
45
|
-
const {
|
|
44
|
+
const { keyId, privateKey, publicKey, purpose } = await getKeyWithMetadata(address)
|
|
46
45
|
|
|
47
46
|
const isTaprootInput = bip371.isTaprootInput(input)
|
|
48
47
|
const isTapLeafScriptSpend = input.tapLeafScript && input.tapLeafScript.length > 0
|
|
@@ -74,8 +73,8 @@ export function createSignWithWallet({
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
const asyncSigner = signer
|
|
77
|
-
? await toAsyncBufferSigner({ signer, isTaprootKeySpend,
|
|
78
|
-
: toAsyncSigner({
|
|
76
|
+
? await toAsyncBufferSigner({ signer, isTaprootKeySpend, keyId })
|
|
77
|
+
: toAsyncSigner({ privateKey, isTaprootKeySpend })
|
|
79
78
|
|
|
80
79
|
// desktop / BE / mobile with bip-schnorr signing
|
|
81
80
|
signingPromises.push(psbt.signInputAsync(index, asyncSigner, allowedSigHashTypes))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/bitcoinjs'
|
|
1
2
|
import assert from 'minimalistic-assert'
|
|
2
3
|
|
|
3
4
|
import { extractTransaction } from './common.js'
|
|
@@ -10,6 +11,8 @@ export const signTxFactory = ({
|
|
|
10
11
|
coinInfo,
|
|
11
12
|
network,
|
|
12
13
|
getKeyIdentifier,
|
|
14
|
+
Psbt = DefaultPsbt,
|
|
15
|
+
Transaction = DefaultTransaction,
|
|
13
16
|
}) => {
|
|
14
17
|
assert(assetName, 'assetName is required')
|
|
15
18
|
assert(resolvePurpose, 'resolvePurpose is required')
|
|
@@ -19,6 +22,8 @@ export const signTxFactory = ({
|
|
|
19
22
|
assetName,
|
|
20
23
|
resolvePurpose,
|
|
21
24
|
coinInfo,
|
|
25
|
+
Psbt,
|
|
26
|
+
Transaction,
|
|
22
27
|
})
|
|
23
28
|
|
|
24
29
|
return async ({ unsignedTx, hdkeys, privateKeysAddressMap, signer }) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Psbt, Transaction } from '@exodus/bitcoinjs'
|
|
1
|
+
import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/bitcoinjs'
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
|
|
4
4
|
const _MAXIMUM_FEE_RATES = {
|
|
@@ -11,7 +11,13 @@ const _MAXIMUM_FEE_RATES = {
|
|
|
11
11
|
* @param { assetName, resolvePurpose, coinInfo} dependencies
|
|
12
12
|
* @returns A prepareForSigning function that returns a PSBTv1 as buffer
|
|
13
13
|
*/
|
|
14
|
-
export function createPrepareForSigning({
|
|
14
|
+
export function createPrepareForSigning({
|
|
15
|
+
assetName,
|
|
16
|
+
resolvePurpose,
|
|
17
|
+
coinInfo,
|
|
18
|
+
Psbt = DefaultPsbt,
|
|
19
|
+
Transaction = DefaultTransaction,
|
|
20
|
+
}) {
|
|
15
21
|
assert(assetName, 'assetName is required')
|
|
16
22
|
assert(resolvePurpose, 'resolvePurpose is required')
|
|
17
23
|
assert(coinInfo, 'coinInfo is required')
|
|
@@ -26,6 +32,7 @@ export function createPrepareForSigning({ assetName, resolvePurpose, coinInfo })
|
|
|
26
32
|
if (isPsbtBufferPassed) {
|
|
27
33
|
// PSBT created externally (Web3, etc..)
|
|
28
34
|
return createPsbtFromBuffer({
|
|
35
|
+
Psbt,
|
|
29
36
|
psbtBuffer: unsignedTx.txData.psbtBuffer,
|
|
30
37
|
networkInfo,
|
|
31
38
|
})
|
|
@@ -37,6 +44,8 @@ export function createPrepareForSigning({ assetName, resolvePurpose, coinInfo })
|
|
|
37
44
|
...unsignedTx.txMeta,
|
|
38
45
|
resolvePurpose,
|
|
39
46
|
networkInfo,
|
|
47
|
+
Psbt,
|
|
48
|
+
Transaction,
|
|
40
49
|
})
|
|
41
50
|
if (!['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) psbt.setVersion(1)
|
|
42
51
|
|
|
@@ -45,12 +54,21 @@ export function createPrepareForSigning({ assetName, resolvePurpose, coinInfo })
|
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
// Creates a PSBT instance from the passed transaction buffer provided by 3rd parties (e.g. dApps).
|
|
48
|
-
function createPsbtFromBuffer({ psbtBuffer, ecc, networkInfo }) {
|
|
57
|
+
function createPsbtFromBuffer({ Psbt, psbtBuffer, ecc, networkInfo }) {
|
|
49
58
|
return Psbt.fromBuffer(psbtBuffer, { eccLib: ecc, network: networkInfo })
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
// Creates a PSBT instance from the passed inputs, outputs etc. The wallet itself provides this data.
|
|
53
|
-
function createPsbtFromTxData({
|
|
62
|
+
function createPsbtFromTxData({
|
|
63
|
+
inputs,
|
|
64
|
+
outputs,
|
|
65
|
+
rawTxs,
|
|
66
|
+
networkInfo,
|
|
67
|
+
resolvePurpose,
|
|
68
|
+
assetName,
|
|
69
|
+
Psbt,
|
|
70
|
+
Transaction,
|
|
71
|
+
}) {
|
|
54
72
|
// use harcoded max fee rates for specific assets
|
|
55
73
|
// if undefined, will be set to default value by PSBT (2500)
|
|
56
74
|
const maximumFeeRate = _MAXIMUM_FEE_RATES[assetName]
|
|
@@ -79,7 +97,7 @@ function createPsbtFromTxData({ inputs, outputs, rawTxs, networkInfo, resolvePur
|
|
|
79
97
|
// non-witness outptus require the full transaction
|
|
80
98
|
assert(!!rawTx, 'Non-witness outputs require the full previous transaction.')
|
|
81
99
|
const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
|
|
82
|
-
if (canParseTx(rawTxBuffer)) {
|
|
100
|
+
if (canParseTx(Transaction, rawTxBuffer)) {
|
|
83
101
|
txIn.nonWitnessUtxo = rawTxBuffer
|
|
84
102
|
} else {
|
|
85
103
|
// temp fix for https://exodusio.slack.com/archives/CP202D90Q/p1671014704829939 until bitcoinjs could parse a mweb tx without failing
|
|
@@ -99,7 +117,7 @@ function createPsbtFromTxData({ inputs, outputs, rawTxs, networkInfo, resolvePur
|
|
|
99
117
|
return psbt
|
|
100
118
|
}
|
|
101
119
|
|
|
102
|
-
const canParseTx = (rawTxBuffer) => {
|
|
120
|
+
const canParseTx = (Transaction, rawTxBuffer) => {
|
|
103
121
|
try {
|
|
104
122
|
Transaction.fromBuffer(rawTxBuffer)
|
|
105
123
|
return true
|
|
@@ -76,7 +76,7 @@ function createSignWithHardwareWallet({ assetName, resolvePurpose }) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function getDerivationPathsMap({ resolvePurpose, accountIndex, addressPathsMap }) {
|
|
79
|
-
const derivationPathsMap =
|
|
79
|
+
const derivationPathsMap = Object.create(null)
|
|
80
80
|
for (const [address, path] of Object.entries(addressPathsMap)) {
|
|
81
81
|
const purpose = resolvePurpose(address)
|
|
82
82
|
const derivationPath = `m/${purpose}'/0'/${accountIndex}'/${path.slice(2)}` // TODO: coinindex
|
package/src/tx-sign/taproot.js
CHANGED
|
@@ -1,67 +1,60 @@
|
|
|
1
|
-
import { crypto
|
|
2
|
-
import
|
|
1
|
+
import { crypto } from '@exodus/bitcoinjs'
|
|
2
|
+
import * as secp256k1 from '@exodus/crypto/secp256k1'
|
|
3
3
|
import assert from 'minimalistic-assert'
|
|
4
4
|
|
|
5
5
|
import defaultEntropy from './default-entropy.cjs'
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
assert(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (signer.publicKey[0] === 3) {
|
|
16
|
-
privateKey = ecc.privateNegate(privateKey)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(signer.publicKey, tweakHash))
|
|
20
|
-
if (!tweakedPrivateKey) {
|
|
21
|
-
throw new Error('Invalid tweaked private key!')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
|
|
25
|
-
network,
|
|
26
|
-
})
|
|
7
|
+
function tweakPrivateKey({ privateKey, tweakHash }) {
|
|
8
|
+
assert(privateKey, 'Private key is required for tweaking signer!')
|
|
9
|
+
const publicKey = secp256k1.privateKeyToPublicKey({ privateKey, format: 'buffer' })
|
|
10
|
+
if (publicKey[0] === 3) privateKey = secp256k1.privateKeyTweakNegate({ privateKey })
|
|
11
|
+
const tweak = tapTweakHash(publicKey, tweakHash)
|
|
12
|
+
return secp256k1.privateKeyTweakAdd({ privateKey, tweak, format: 'buffer' })
|
|
27
13
|
}
|
|
28
14
|
|
|
29
15
|
export const tweakPublicKey = ({ publicKey, tweak }) => {
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
return Buffer.from([parity ? 0x03 : 0x02, ...xOnlyPubkey])
|
|
16
|
+
// This is different from secp256k1.publicKeyTweakAddScalar when publicKey[0] is 3
|
|
17
|
+
const xOnly = secp256k1.publicKeyToX({ publicKey })
|
|
18
|
+
return secp256k1.xOnlyTweakAdd({ xOnly, tweak, format: 'buffer' })
|
|
34
19
|
}
|
|
35
20
|
|
|
36
21
|
export const tapTweakHash = (publicKey, h) => {
|
|
37
|
-
const
|
|
38
|
-
return crypto.taggedHash('TapTweak', Buffer.concat(h ? [
|
|
22
|
+
const xOnly = secp256k1.publicKeyToX({ publicKey })
|
|
23
|
+
return crypto.taggedHash('TapTweak', Buffer.concat(h ? [xOnly, h] : [xOnly]))
|
|
39
24
|
}
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (isTaprootKeySpend) {
|
|
48
|
-
keyPair = tweakSigner({ signer: keyPair, network })
|
|
26
|
+
export function toAsyncSigner({ keyPair, privateKey, isTaprootKeySpend }) {
|
|
27
|
+
if (keyPair && !privateKey) {
|
|
28
|
+
// compat mode to avoid a breaking change
|
|
29
|
+
// TODO: remove in semver-major
|
|
30
|
+
privateKey = keyPair.privateKey
|
|
31
|
+
keyPair = null
|
|
49
32
|
}
|
|
50
33
|
|
|
51
|
-
|
|
52
|
-
keyPair
|
|
53
|
-
const sig = ecc.sign(h, keyPair.privateKey)
|
|
54
|
-
return Buffer.from(sig)
|
|
55
|
-
}
|
|
34
|
+
assert(privateKey, 'privateKey is required')
|
|
35
|
+
assert(!keyPair, 'keyPair is not supported, use privateKey')
|
|
56
36
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// defaultEntropy.getSchnorrEntropy() is mockable with jest.spyOn
|
|
60
|
-
const sig = ecc.signSchnorr(h, keyPair.privateKey, defaultEntropy.getSchnorrEntropy())
|
|
61
|
-
return Buffer.from(sig)
|
|
62
|
-
}
|
|
37
|
+
if (isTaprootKeySpend) privateKey = tweakPrivateKey({ privateKey })
|
|
38
|
+
const publicKey = secp256k1.privateKeyToPublicKey({ privateKey, format: 'buffer' })
|
|
63
39
|
|
|
64
|
-
return
|
|
40
|
+
return {
|
|
41
|
+
sign: async (hash) =>
|
|
42
|
+
secp256k1.ecdsaSignHash({
|
|
43
|
+
hash,
|
|
44
|
+
privateKey,
|
|
45
|
+
extraEntropy: null, // TODO: can we flip this to true?
|
|
46
|
+
format: 'buffer',
|
|
47
|
+
}),
|
|
48
|
+
signSchnorr: async (data) =>
|
|
49
|
+
secp256k1.schnorrSign({
|
|
50
|
+
data,
|
|
51
|
+
privateKey,
|
|
52
|
+
extraEntropy: defaultEntropy.getSchnorrEntropy(), // mockable with jest.spyOn
|
|
53
|
+
format: 'buffer',
|
|
54
|
+
}),
|
|
55
|
+
publicKey,
|
|
56
|
+
privateKey,
|
|
57
|
+
}
|
|
65
58
|
}
|
|
66
59
|
|
|
67
60
|
// signer: {
|
|
@@ -69,7 +62,7 @@ export function toAsyncSigner({ keyPair, isTaprootKeySpend, network }) {
|
|
|
69
62
|
// getPublicKey: ({ keyId }) => Promise<Buffer>
|
|
70
63
|
// }
|
|
71
64
|
//
|
|
72
|
-
export async function toAsyncBufferSigner({ signer,
|
|
65
|
+
export async function toAsyncBufferSigner({ signer, keyId, isTaprootKeySpend }) {
|
|
73
66
|
let tweak
|
|
74
67
|
let publicKey = await signer.getPublicKey({ keyId })
|
|
75
68
|
if (isTaprootKeySpend) {
|
|
@@ -79,12 +72,7 @@ export async function toAsyncBufferSigner({ signer, purpose, keyId, isTaprootKey
|
|
|
79
72
|
|
|
80
73
|
return {
|
|
81
74
|
sign: async (data) => {
|
|
82
|
-
|
|
83
|
-
const sig = await signer.sign({ data, keyId, ecOptions, enc: 'raw', signatureType: 'ecdsa' })
|
|
84
|
-
const signature = new Uint8Array(64)
|
|
85
|
-
signature.set(sig.r.toArrayLike(Uint8Array, 'be', 32), 0)
|
|
86
|
-
signature.set(sig.s.toArrayLike(Uint8Array, 'be', 32), 32)
|
|
87
|
-
return Buffer.from(signature)
|
|
75
|
+
return signer.sign({ data, keyId, enc: 'sig', signatureType: 'ecdsa' })
|
|
88
76
|
},
|
|
89
77
|
signSchnorr: async (data) => {
|
|
90
78
|
return signer.sign({
|
package/src/utxos-utils.js
CHANGED
|
@@ -200,9 +200,16 @@ export function partitionUtxos({
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
export const isUtxoConfirmed = (utxo) => utxo.confirmations > 0
|
|
204
|
+
|
|
203
205
|
export function getConfirmedUtxos({ utxos }) {
|
|
204
206
|
assert(utxos, 'utxos is required')
|
|
205
|
-
return utxos.filter(
|
|
207
|
+
return utxos.filter(isUtxoConfirmed)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getUnconfirmedUtxos({ utxos }) {
|
|
211
|
+
assert(utxos, 'utxos is required')
|
|
212
|
+
return utxos.filter((utxo) => !isUtxoConfirmed(utxo))
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
export function getConfirmedOrRfbDisabledUtxos({ utxos, allowUnconfirmedRbfEnabledUtxos }) {
|