@exodus/bitcoin-api 2.23.0 → 2.24.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,20 @@
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.24.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.23.0...@exodus/bitcoin-api@2.24.0) (2024-09-13)
7
+
8
+
9
+ ### Features
10
+
11
+ * **BTC:** add multisig data to hardware wallet signing ([#3633](https://github.com/ExodusMovement/assets/issues/3633)) ([90293e9](https://github.com/ExodusMovement/assets/commit/90293e9b80799556df1c595558b1544c5a081d7b))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **BTC:** allow internal pubkey and sort xonly for multisig ([#3634](https://github.com/ExodusMovement/assets/issues/3634)) ([808bf65](https://github.com/ExodusMovement/assets/commit/808bf65f05576e2758bb32ad5654143577db7c38))
17
+
18
+
19
+
6
20
  ## [2.23.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.22.1...@exodus/bitcoin-api@2.23.0) (2024-09-11)
7
21
 
8
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "2.23.0",
3
+ "version": "2.24.0",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -61,5 +61,5 @@
61
61
  "type": "git",
62
62
  "url": "git+https://github.com/ExodusMovement/assets.git"
63
63
  },
64
- "gitHead": "e9a0341b6e59b226e46b0e802ea1240c55724f68"
64
+ "gitHead": "ba34f68e632d17fdd80cc50358792f449e92180a"
65
65
  }
@@ -5,14 +5,14 @@ import { toXOnly } from './bitcoinjs-lib/ecc-utils.js'
5
5
 
6
6
  // Key to use when key path spending is disabled https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs
7
7
  const DUMMY_TAPROOT_PUBKEY = Buffer.from(
8
- '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0',
8
+ '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0',
9
9
  'hex'
10
10
  )
11
11
 
12
12
  // Leaf version for BIP342 is 0xc0 or 192 https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#specification
13
13
  const LEAF_VERSION_TAPSCRIPT = 192
14
14
 
15
- // Limit multisig keys to 20 for now
15
+ // Limit multisig keys to 16 for now
16
16
  const MAX_PUBKEYS = 20
17
17
 
18
18
  export const createEncodeMultisigContract =
@@ -21,7 +21,14 @@ export const createEncodeMultisigContract =
21
21
  network = bitcoinjsLib.Network.bitcoin,
22
22
  ecc = defaultEcc,
23
23
  }) =>
24
- (publicKeys, { threshold = publicKeys.length, version = 0 } = Object.create(null)) => {
24
+ (
25
+ publicKeys,
26
+ {
27
+ threshold = publicKeys.length,
28
+ version = 0,
29
+ internalPubkey = DUMMY_TAPROOT_PUBKEY,
30
+ } = Object.create(null)
31
+ ) => {
25
32
  if (
26
33
  !Array.isArray(publicKeys) ||
27
34
  publicKeys.some((k) => !Buffer.isBuffer(k) || !ecc.isPointCompressed(k))
@@ -57,7 +64,7 @@ export const createEncodeMultisigContract =
57
64
  }
58
65
 
59
66
  // Sort according to BIP67 https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki
60
- publicKeys.sort((a, b) => Buffer.compare(a, b))
67
+ publicKeys.sort((a, b) => Buffer.compare(toXOnly(a), toXOnly(b)))
61
68
 
62
69
  // Create multisig redeem script https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#cite_note-5
63
70
  const OPS = bitcoinjsLib.script.OPS
@@ -74,7 +81,7 @@ export const createEncodeMultisigContract =
74
81
  const output = bitcoinjsLib.script.compile(chunks)
75
82
 
76
83
  return bitcoinjsLib.payments.p2tr({
77
- internalPubkey: DUMMY_TAPROOT_PUBKEY,
84
+ internalPubkey: toXOnly(internalPubkey),
78
85
  scriptTree: { output },
79
86
  redeem: { output, redeemVersion: LEAF_VERSION_TAPSCRIPT },
80
87
  network,
@@ -20,7 +20,7 @@ export const signHardwareFactory = ({ assetName, resolvePurpose, keys, coinInfo
20
20
  resolvePurpose,
21
21
  })
22
22
 
23
- return async ({ unsignedTx, hardwareDevice, accountIndex }) => {
23
+ return async ({ unsignedTx, hardwareDevice, accountIndex, multisigData }) => {
24
24
  assert(unsignedTx, 'unsignedTx is required')
25
25
  assert(hardwareDevice, 'hardwareDevice is required')
26
26
  assert(Number.isInteger(accountIndex), 'accountIndex must be integer')
@@ -37,35 +37,65 @@ export const signHardwareFactory = ({ assetName, resolvePurpose, keys, coinInfo
37
37
  addressPathsMap,
38
38
  hardwareDevice,
39
39
  accountIndex,
40
+ multisigData,
40
41
  })
41
42
 
42
- const skipFinalize = !!unsignedTx.txData.psbtBuffer
43
+ const skipFinalize = !!unsignedTx.txData.psbtBuffer || unsignedTx.txMeta.returnPsbt
43
44
  return extractTransaction({ psbt, skipFinalize })
44
45
  }
45
46
  }
46
47
 
47
48
  function createSignWithHardwareWallet({ assetName, resolvePurpose }) {
48
- return async ({ psbt, inputsToSign, addressPathsMap, accountIndex, hardwareDevice }) => {
49
- const derivationPaths = getDerivationPaths({ resolvePurpose, addressPathsMap, accountIndex })
49
+ return async ({
50
+ psbt,
51
+ inputsToSign,
52
+ addressPathsMap,
53
+ accountIndex,
54
+ hardwareDevice,
55
+ multisigData,
56
+ }) => {
57
+ const derivationPathsMap = getDerivationPathsMap({
58
+ resolvePurpose,
59
+ addressPathsMap,
60
+ accountIndex,
61
+ })
50
62
  const signatures = await hardwareDevice.signTransaction({
51
63
  assetName,
52
64
  signableTransaction: psbt.toBuffer(),
53
- derivationPaths,
65
+ derivationPaths: Object.values(derivationPathsMap),
66
+ derivationPathsMap,
67
+ multisigData,
54
68
  })
55
69
 
56
- applySignatures(psbt, signatures, inputsToSign)
70
+ if (multisigData) {
71
+ applyMultisigSignatures(psbt, signatures)
72
+ } else {
73
+ applySignatures(psbt, signatures, inputsToSign)
74
+ }
57
75
  }
58
76
  }
59
77
 
60
- function getDerivationPaths({ resolvePurpose, accountIndex, addressPathsMap }) {
61
- const derivationPaths = []
78
+ function getDerivationPathsMap({ resolvePurpose, accountIndex, addressPathsMap }) {
79
+ const derivationPathsMap = {}
62
80
  for (const [address, path] of Object.entries(addressPathsMap)) {
63
81
  const purpose = resolvePurpose(address)
64
82
  const derivationPath = `m/${purpose}'/0'/${accountIndex}'/${path.slice(2)}` // TODO: coinindex
65
- derivationPaths.push(derivationPath)
83
+ derivationPathsMap[address] = derivationPath
66
84
  }
67
85
 
68
- return derivationPaths
86
+ return derivationPathsMap
87
+ }
88
+
89
+ function applyMultisigSignatures(psbt, signatures) {
90
+ for (const signature of signatures) {
91
+ const input = psbt.data.inputs[signature.inputIndex]
92
+ if (!input.tapScriptSig) input.tapScriptSig = []
93
+ input.tapScriptSig.push({
94
+ pubkey: signature.publicKey,
95
+ signature: signature.signature,
96
+ leafHash: signature.tapleafHash,
97
+ })
98
+ }
69
99
  }
70
100
 
71
101
  export function applySignatures(psbt, signatures, inputsToSign) {