@exodus/bitcoin-api 2.32.0 → 2.33.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,16 @@
|
|
|
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.33.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.32.0...@exodus/bitcoin-api@2.33.0) (2025-06-30)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* feat: enable zcash buffer signer (#5732)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
## [2.32.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.31.1...@exodus/bitcoin-api@2.32.0) (2025-04-01)
|
|
7
17
|
|
|
8
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.33.0",
|
|
4
4
|
"description": "Bitcoin transaction and fee monitors, RPC with the blockchain node, other networking code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@exodus/simple-retry": "^0.0.6",
|
|
35
35
|
"bech32": "^1.1.3",
|
|
36
36
|
"bip32-path": "^0.4.2",
|
|
37
|
-
"bs58check": "^
|
|
37
|
+
"bs58check": "^3.0.1",
|
|
38
38
|
"delay": "^4.0.1",
|
|
39
39
|
"lodash": "^4.17.21",
|
|
40
40
|
"minimalistic-assert": "^1.0.1",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"type": "git",
|
|
57
57
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "b680523e8d9e053a441e4ce863c83ada389b7a73"
|
|
60
60
|
}
|
package/src/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export { createEncodeMultisigContract } from './multisig-address.js'
|
|
|
24
24
|
export { toAsyncSigner } from './tx-sign/taproot.js'
|
|
25
25
|
export * from './ordinals-utils.js'
|
|
26
26
|
export { signMessage, signMessageWithSigner } from './sign-message.js'
|
|
27
|
+
export { writePsbtBlockHeight, readPsbtBlockHeight } from './psbt-proprietary-types.js'
|
|
27
28
|
|
|
28
29
|
// TODO: remove these, kept for compat
|
|
29
30
|
export { scriptClassify } from '@exodus/bitcoinjs'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Set of utilities to add proprietary use types to PSBT
|
|
3
|
+
* see https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#proprietary-use-type
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import assert from 'minimalistic-assert'
|
|
7
|
+
import varuint from 'varuint-bitcoin'
|
|
8
|
+
|
|
9
|
+
const IDENTIFIER = 'exodus'
|
|
10
|
+
const PROP_TYPE_MARKER = 0xfc
|
|
11
|
+
|
|
12
|
+
export const SubType = Object.freeze({
|
|
13
|
+
BlockHeight: 0x01,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const buildPropKey = (subType) => {
|
|
17
|
+
const idBuf = Buffer.from(IDENTIFIER, 'utf8')
|
|
18
|
+
const parts = [
|
|
19
|
+
Buffer.from([PROP_TYPE_MARKER]),
|
|
20
|
+
varuint.encode(idBuf.length),
|
|
21
|
+
idBuf,
|
|
22
|
+
varuint.encode(subType),
|
|
23
|
+
]
|
|
24
|
+
return Buffer.concat(parts)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const u32LE = (n) => {
|
|
28
|
+
const b = Buffer.allocUnsafe(4)
|
|
29
|
+
b.writeUInt32LE(n >>> 0, 0)
|
|
30
|
+
return b
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const findProprietaryVal = (unknownArr, subType) => {
|
|
34
|
+
if (!unknownArr) return
|
|
35
|
+
|
|
36
|
+
const prefix = buildPropKey(subType)
|
|
37
|
+
|
|
38
|
+
return unknownArr.find((kv) => kv.key.equals(prefix))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const writePsbtBlockHeight = (psbt, height) => {
|
|
42
|
+
assert(
|
|
43
|
+
Number.isInteger(height) && height >= 0 && height <= 4_294_967_295,
|
|
44
|
+
'blockHeight must be a positive number between 0 and 4294967295'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
psbt.addUnknownKeyValToGlobal({ key: buildPropKey(SubType.BlockHeight), value: u32LE(height) })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const readPsbtBlockHeight = (psbt) => {
|
|
51
|
+
const kv = findProprietaryVal(psbt.data.globalMap.unknownKeyVals, SubType.BlockHeight)
|
|
52
|
+
return kv ? kv.value.readUInt32LE(0) : undefined
|
|
53
|
+
}
|
|
@@ -39,7 +39,18 @@ export const createGetKeyWithMetadata = ({
|
|
|
39
39
|
function getPrivateKeyFromMap(privateKeysAddressMap, networkInfo, purpose, address) {
|
|
40
40
|
const privateWif = getOwnProperty(privateKeysAddressMap, address, 'string')
|
|
41
41
|
assert(privateWif, `there is no private key for address ${address}`)
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
// ECPair.fromWIF() rejects network objects whose pubKeyHash/scriptHash are wider than one byte (UInt8).
|
|
44
|
+
// so we skip the network argument in that case and let the library infer it from the WIF prefix.
|
|
45
|
+
const useNet =
|
|
46
|
+
networkInfo && networkInfo.pubKeyHash <= 0xff && networkInfo.scriptHash <= 0xff
|
|
47
|
+
? networkInfo
|
|
48
|
+
: undefined
|
|
49
|
+
|
|
50
|
+
const { privateKey, compressed } = useNet
|
|
51
|
+
? ECPair.fromWIF(privateWif, useNet)
|
|
52
|
+
: ECPair.fromWIF(privateWif)
|
|
53
|
+
|
|
43
54
|
const publicKey = privateKeyToPublicKey({ privateKey, compressed, format: 'buffer' })
|
|
44
55
|
return { privateKey, publicKey, purpose }
|
|
45
56
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/bitcoinjs'
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
|
|
4
|
+
import { writePsbtBlockHeight } from '../psbt-proprietary-types.js'
|
|
5
|
+
|
|
4
6
|
const _MAXIMUM_FEE_RATES = {
|
|
5
7
|
qtumignition: 25_000,
|
|
6
8
|
ravencoin: 1_000_000,
|
|
@@ -40,6 +42,7 @@ export function createPrepareForSigning({
|
|
|
40
42
|
|
|
41
43
|
// Create PSBT based on internal Exodus data structure
|
|
42
44
|
const psbt = createPsbtFromTxData({
|
|
45
|
+
assetName,
|
|
43
46
|
...unsignedTx.txData,
|
|
44
47
|
...unsignedTx.txMeta,
|
|
45
48
|
resolvePurpose,
|
|
@@ -68,6 +71,7 @@ function createPsbtFromTxData({
|
|
|
68
71
|
assetName,
|
|
69
72
|
Psbt,
|
|
70
73
|
Transaction,
|
|
74
|
+
blockHeight,
|
|
71
75
|
}) {
|
|
72
76
|
// use harcoded max fee rates for specific assets
|
|
73
77
|
// if undefined, will be set to default value by PSBT (2500)
|
|
@@ -75,6 +79,11 @@ function createPsbtFromTxData({
|
|
|
75
79
|
|
|
76
80
|
const psbt = new Psbt({ maximumFeeRate, network: networkInfo })
|
|
77
81
|
|
|
82
|
+
// If present, add blockHeight as a proprietary field
|
|
83
|
+
if (blockHeight) {
|
|
84
|
+
writePsbtBlockHeight(psbt, blockHeight)
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
// Fill tx
|
|
79
88
|
for (const { txId, vout, address, value, script, sequence, tapLeafScript } of inputs) {
|
|
80
89
|
// TODO: don't use the purpose as intermediate variable
|
|
@@ -83,9 +92,15 @@ function createPsbtFromTxData({
|
|
|
83
92
|
|
|
84
93
|
const isSegwitAddress = purpose === 84
|
|
85
94
|
const isTaprootAddress = purpose === 86
|
|
95
|
+
const isZcashAsset = assetName === 'zcash'
|
|
86
96
|
|
|
87
97
|
const txIn = { hash: txId, index: vout, sequence }
|
|
88
98
|
|
|
99
|
+
if (isZcashAsset) {
|
|
100
|
+
txIn.script = script
|
|
101
|
+
txIn.value = value
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
if (isTaprootAddress && tapLeafScript) {
|
|
90
105
|
txIn.tapLeafScript = tapLeafScript
|
|
91
106
|
}
|
|
@@ -97,12 +112,9 @@ function createPsbtFromTxData({
|
|
|
97
112
|
|
|
98
113
|
const rawTx = (rawTxs || []).find((t) => t.txId === txId)
|
|
99
114
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`Non-taproot outputs require the full previous transaction.`
|
|
104
|
-
)
|
|
105
|
-
if (!isTaprootAddress) {
|
|
115
|
+
if (!isTaprootAddress && !isZcashAsset) {
|
|
116
|
+
assert(!!rawTx?.rawData, `Non-taproot outputs require the full previous transaction.`)
|
|
117
|
+
|
|
106
118
|
const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
|
|
107
119
|
if (canParseTx(Transaction, rawTxBuffer)) {
|
|
108
120
|
txIn.nonWitnessUtxo = rawTxBuffer
|
package/src/tx-sign/taproot.js
CHANGED
|
@@ -47,6 +47,19 @@ export function toAsyncSigner({ privateKey, publicKey, isTaprootKeySpend }) {
|
|
|
47
47
|
extraEntropy: defaultEntropy.getSchnorrEntropy(), // mockable with jest.spyOn
|
|
48
48
|
format: 'buffer',
|
|
49
49
|
}),
|
|
50
|
+
signEncoded: async ({ data, enc = 'sig' }) => {
|
|
51
|
+
assert(
|
|
52
|
+
['der', 'sig'].includes(enc),
|
|
53
|
+
'signBuffer: invalid encoding type. Expected "der" or "sig".'
|
|
54
|
+
)
|
|
55
|
+
return secp256k1.ecdsaSignHash({
|
|
56
|
+
hash: data,
|
|
57
|
+
der: enc === 'der',
|
|
58
|
+
privateKey,
|
|
59
|
+
extraEntropy: null,
|
|
60
|
+
format: 'buffer',
|
|
61
|
+
})
|
|
62
|
+
},
|
|
50
63
|
publicKey,
|
|
51
64
|
privateKey,
|
|
52
65
|
}
|
|
@@ -79,6 +92,9 @@ export async function toAsyncBufferSigner({ signer, keyId, isTaprootKeySpend })
|
|
|
79
92
|
extraEntropy: defaultEntropy.getSchnorrEntropy(),
|
|
80
93
|
})
|
|
81
94
|
},
|
|
95
|
+
signEncoded: async ({ data, enc = 'sig' }) => {
|
|
96
|
+
return signer.sign({ data, keyId, enc, signatureType: 'ecdsa' })
|
|
97
|
+
},
|
|
82
98
|
publicKey,
|
|
83
99
|
}
|
|
84
100
|
}
|