@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 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.26.1",
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.1.0",
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.12",
30
+ "@exodus/crypto": "^1.0.0-rc.13",
31
31
  "@exodus/currency": "^5.0.2",
32
- "@exodus/key-identifier": "^1.1.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": "3258bee267b1a1d64ddf62bc17f66a9fc07c9329"
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,
@@ -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(({ confirmations }) => confirmations > 0))
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)
@@ -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(({ confirmations }) => !confirmations || confirmations <= 0)
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({
@@ -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 = 20
16
+ const MAX_PUBKEYS = 16
17
17
 
18
18
  export const createEncodeMultisigContract =
19
19
  ({ bitcoinjsLib = defaultBitcoinjsLib, network = bitcoinjsLib.Network.bitcoin, ecc }) =>
@@ -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 parsedValue = Buffer.isBuffer(value) ? new BN(value, 10, 'le') : value // handle Dogecoin buffer values
14
- return asset.currency.baseUnit(parsedValue).toDefault()
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 = assetName === 'dogecoin' ? output.valueBuffer : output.value
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 privateKey = getOwnProperty(privateKeysAddressMap, address, 'string')
34
- assert(privateKey, `there is no private key for address ${address}`)
35
- const key = ECPair.fromWIF(privateKey, networkInfo)
36
- const publicKey = privateKeyToPublicKey({ privateKey: key.privateKey, format: 'buffer' })
37
- return { key, purpose, publicKey }
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 key = ECPair.fromPrivateKey(derivedhdkey.privateKey, { network: networkInfo })
47
+ const { privateKey } = ECPair.fromPrivateKey(derivedhdkey.privateKey, { network: networkInfo })
48
48
  const publicKey = derivedhdkey.publicKey
49
- return { key, publicKey, purpose }
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 { purpose, keyId, publicKey }
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 { key, purpose, keyId, publicKey } = await getKeyWithMetadata(address)
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, purpose, keyId })
78
- : toAsyncSigner({ keyPair: key, isTaprootKeySpend, network })
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({ assetName, resolvePurpose, coinInfo }) {
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({ inputs, outputs, rawTxs, networkInfo, resolvePurpose, assetName }) {
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
@@ -1,67 +1,60 @@
1
- import { crypto, ECPair } from '@exodus/bitcoinjs'
2
- import { tiny_secp256k1_compat as ecc } from '@exodus/crypto/secp256k1'
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 tweakSigner({ signer, tweakHash, network }) {
8
- assert(signer, 'signer is required')
9
-
10
- let privateKey = signer.privateKey
11
- if (!privateKey) {
12
- throw new Error('Private key is required for tweaking signer!')
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
- const xOnlyPub = ecc.xOnlyPointFromPoint(publicKey)
31
- const { parity, xOnlyPubkey } = ecc.xOnlyPointAddTweak(xOnlyPub, tweak)
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 xOnlyPoint = ecc.xOnlyPointFromPoint(publicKey)
38
- return crypto.taggedHash('TapTweak', Buffer.concat(h ? [xOnlyPoint, h] : [xOnlyPoint]))
22
+ const xOnly = secp256k1.publicKeyToX({ publicKey })
23
+ return crypto.taggedHash('TapTweak', Buffer.concat(h ? [xOnly, h] : [xOnly]))
39
24
  }
40
25
 
41
- /**
42
- * Take a sync signer and make it async.
43
- */
44
- export function toAsyncSigner({ keyPair, isTaprootKeySpend, network }) {
45
- assert(keyPair, 'keyPair is required')
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
- // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
52
- keyPair.sign = async (h) => {
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
- // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
58
- keyPair.signSchnorr = async (h) => {
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 keyPair
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, purpose, keyId, isTaprootKeySpend }) {
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
- const ecOptions = { canonical: true }
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({
@@ -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(({ confirmations }) => confirmations > 0)
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 }) {