Skip to content

Commit d39151f

Browse files
committed
Merge branch 'develop' into 'master'
Develop See merge request papers/airgap/airgap-vault!537
2 parents 289fead + 29ee136 commit d39151f

File tree

11 files changed

+474
-426
lines changed

11 files changed

+474
-426
lines changed

package.json

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"build:analyze": "yarn build:stats && webpack-bundle-analyzer ./www/stats.json",
1818
"build:electron": "ng build --base-href=./",
1919
"build:electron:prod": "ng build --base-href=./ --configuration production",
20+
"memory": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --port 4201",
2021
"test": "ng test",
2122
"test-ci": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng test --code-coverage --watch=false",
2223
"lint": "ng lint",
@@ -36,51 +37,51 @@
3637
"apply-diagnostic-modules": "node apply-diagnostic-modules.js"
3738
},
3839
"resolutions": {
39-
"@airgap/aeternity": "0.13.41",
40-
"@airgap/acurast": "0.13.41",
41-
"@airgap/astar": "0.13.41",
42-
"@airgap/bitcoin": "0.13.41",
43-
"@airgap/coinlib-core": "0.13.41",
44-
"@airgap/coreum": "0.13.41",
45-
"@airgap/cosmos": "0.13.41",
46-
"@airgap/cosmos-core": "0.13.41",
47-
"@airgap/crypto": "0.13.41",
48-
"@airgap/ethereum": "0.13.41",
49-
"@airgap/groestlcoin": "0.13.41",
50-
"@airgap/icp": "0.13.41",
51-
"@airgap/module-kit": "0.13.41",
52-
"@airgap/moonbeam": "0.13.41",
53-
"@airgap/optimism": "0.13.41",
54-
"@airgap/polkadot": "0.13.41",
55-
"@airgap/serializer": "0.13.41",
56-
"@airgap/stellar": "0.13.41",
57-
"@airgap/substrate": "0.13.41",
58-
"@airgap/tezos": "0.13.41"
40+
"@airgap/aeternity": "0.13.42",
41+
"@airgap/acurast": "0.13.42",
42+
"@airgap/astar": "0.13.42",
43+
"@airgap/bitcoin": "0.13.42",
44+
"@airgap/coinlib-core": "0.13.42",
45+
"@airgap/coreum": "0.13.42",
46+
"@airgap/cosmos": "0.13.42",
47+
"@airgap/cosmos-core": "0.13.42",
48+
"@airgap/crypto": "0.13.42",
49+
"@airgap/ethereum": "0.13.42",
50+
"@airgap/groestlcoin": "0.13.42",
51+
"@airgap/icp": "0.13.42",
52+
"@airgap/module-kit": "0.13.42",
53+
"@airgap/moonbeam": "0.13.42",
54+
"@airgap/optimism": "0.13.42",
55+
"@airgap/polkadot": "0.13.42",
56+
"@airgap/serializer": "0.13.42",
57+
"@airgap/stellar": "0.13.42",
58+
"@airgap/substrate": "0.13.42",
59+
"@airgap/tezos": "0.13.42"
5960
},
6061
"dependencies": {
61-
"@airgap/aeternity": "0.13.41",
62-
"@airgap/acurast": "0.13.41",
63-
"@airgap/angular-core": "0.0.57",
64-
"@airgap/angular-ngrx": "0.0.57",
65-
"@airgap/astar": "0.13.41",
66-
"@airgap/bitcoin": "0.13.41",
67-
"@airgap/coinlib-core": "0.13.41",
68-
"@airgap/coreum": "0.13.41",
69-
"@airgap/cosmos": "0.13.41",
70-
"@airgap/cosmos-core": "0.13.41",
71-
"@airgap/crypto": "0.13.41",
72-
"@airgap/ethereum": "0.13.41",
73-
"@airgap/groestlcoin": "0.13.41",
74-
"@airgap/icp": "0.13.41",
75-
"@airgap/module-kit": "0.13.41",
76-
"@airgap/moonbeam": "0.13.41",
77-
"@airgap/optimism": "0.13.41",
78-
"@airgap/polkadot": "0.13.41",
62+
"@airgap/aeternity": "0.13.42",
63+
"@airgap/acurast": "0.13.42",
64+
"@airgap/angular-core": "0.0.58",
65+
"@airgap/angular-ngrx": "0.0.58",
66+
"@airgap/astar": "0.13.42",
67+
"@airgap/bitcoin": "0.13.42",
68+
"@airgap/coinlib-core": "0.13.42",
69+
"@airgap/coreum": "0.13.42",
70+
"@airgap/cosmos": "0.13.42",
71+
"@airgap/cosmos-core": "0.13.42",
72+
"@airgap/crypto": "0.13.42",
73+
"@airgap/ethereum": "0.13.42",
74+
"@airgap/groestlcoin": "0.13.42",
75+
"@airgap/icp": "0.13.42",
76+
"@airgap/module-kit": "0.13.42",
77+
"@airgap/moonbeam": "0.13.42",
78+
"@airgap/optimism": "0.13.42",
79+
"@airgap/polkadot": "0.13.42",
7980
"@airgap/sapling-wasm": "0.0.7",
80-
"@airgap/serializer": "0.13.41",
81-
"@airgap/stellar": "0.13.41",
82-
"@airgap/substrate": "0.13.41",
83-
"@airgap/tezos": "0.13.41",
81+
"@airgap/serializer": "0.13.42",
82+
"@airgap/stellar": "0.13.42",
83+
"@airgap/substrate": "0.13.42",
84+
"@airgap/tezos": "0.13.42",
8485
"@airgap-community/iso-rootstock": "1.0.0",
8586
"@angular/cdk": "^14.2.7",
8687
"@angular/common": "16.1.1",
@@ -125,7 +126,8 @@
125126
"angularx-qrcode": "^16.0.0",
126127
"axios": "^0.24.0",
127128
"bignumber.js": "^9.0.0",
128-
"bip32": "^2.0.6",
129+
"@bitcoinerlab/secp256k1": "1.2.0",
130+
"bip32": "5.0.0-rc.0",
129131
"bip39": "^3.0.3",
130132
"browserify-zlib": "^0.2.0",
131133
"cordova-plugin-audioinput": "1.0.1",

src/app/models/BIP85.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { BIP32Interface, fromBase58, fromSeed } from 'bip32'
1+
import { BIP32Factory, BIP32Interface } from 'bip32'
2+
import * as ecc from '@bitcoinerlab/secp256k1'
3+
24
import { validateMnemonic, entropyToMnemonic, mnemonicToSeedSync } from 'bip39'
35
import { BIP85Child } from './BIP85Child'
46

@@ -116,7 +118,7 @@ export class BIP85 {
116118

117119
derive(path: string, bytesLength: number = 64): string {
118120
const childNode: BIP32Interface = this.node.derivePath(path)
119-
const childPrivateKey: Buffer = childNode.privateKey! // Child derived from root key always has private key
121+
const childPrivateKey = Buffer.from(childNode.privateKey!) // Child derived from root key always has private key
120122

121123
const hash: Buffer = hmacSHA512(Buffer.from(BIP85_KEY), childPrivateKey)
122124
const truncatedHash: Buffer = hash.slice(0, bytesLength)
@@ -127,7 +129,9 @@ export class BIP85 {
127129
}
128130

129131
static fromBase58(bip32seed: string): BIP85 {
130-
const node: BIP32Interface = fromBase58(bip32seed)
132+
const bip32 = BIP32Factory(ecc)
133+
134+
const node: BIP32Interface = bip32.fromBase58(bip32seed)
131135
if (node.depth !== 0) {
132136
throw new Error('Expected master, got child')
133137
}
@@ -136,7 +140,9 @@ export class BIP85 {
136140
}
137141

138142
static fromSeed(bip32seed: Buffer): BIP85 {
139-
const node: BIP32Interface = fromSeed(bip32seed)
143+
const bip32 = BIP32Factory(ecc)
144+
145+
const node: BIP32Interface = bip32.fromSeed(new Uint8Array(bip32seed))
140146
if (node.depth !== 0) {
141147
throw new Error('Expected master, got child')
142148
}

src/app/models/BIP85Child.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { encode } from 'wif'
2-
import { fromPrivateKey } from 'bip32'
2+
import { BIP32Factory } from 'bip32'
3+
import * as ecc from '@bitcoinerlab/secp256k1'
34
import { entropyToMnemonic } from 'bip39'
45
import { BIP85_APPLICATIONS } from './BIP85'
56

67
export class BIP85Child {
8+
private readonly bip32 = BIP32Factory(ecc)
9+
710
constructor(private readonly entropy: string, private readonly type: BIP85_APPLICATIONS) {}
811

912
toEntropy(): string {
@@ -40,6 +43,6 @@ export class BIP85Child {
4043
const chainCode = Buffer.from(this.entropy.slice(0, 64), 'hex')
4144
const privateKey = Buffer.from(this.entropy.slice(64, 128), 'hex')
4245

43-
return fromPrivateKey(privateKey, chainCode).toBase58()
46+
return this.bip32.fromPrivateKey(new Uint8Array(privateKey), new Uint8Array(chainCode)).toBase58()
4447
}
4548
}

src/app/models/secret.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AirGapWallet } from '@airgap/coinlib-core'
22
import { UUID } from 'angular2-uuid'
3-
import { fromSeed } from 'bip32'
4-
3+
import { BIP32Factory } from 'bip32'
4+
import * as ecc from '@bitcoinerlab/secp256k1'
55
import { toBoolean } from '../utils/utils'
66

77
import { BIPSigner } from './BIP39Signer'
@@ -64,6 +64,8 @@ export class MnemonicSecret extends Secret {
6464

6565
public wallets: AirGapWallet[]
6666

67+
private readonly bip32 = BIP32Factory(ecc)
68+
6769
private readonly twofactor: string
6870

6971
constructor(seed: string | null, label: string = '', isParanoia: boolean = false, hasRecoveryKey: boolean = false) {
@@ -114,7 +116,7 @@ export class MnemonicSecret extends Secret {
114116
const mnemonic: string = this.getMnemonicFromEntropy(entropy)
115117
const seed: Buffer = signer.mnemonicToSeedSync(mnemonic)
116118

117-
return fromSeed(seed).fingerprint.toString('hex')
119+
return Buffer.from(this.bip32.fromSeed(new Uint8Array(seed)).fingerprint).toString('hex')
118120
}
119121

120122
private isMnemonic(data: string): boolean {

src/app/pages/account-address/account-address.page.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ export class AccountAddressPage {
148148
case MainProtocolSymbols.BTC_SEGWIT:
149149
this.syncOptions = [airgapwallet, bluewallet, sparrowwallet, specterwallet, nunchukwallet]
150150
break
151+
case MainProtocolSymbols.BTC_TAPROOT:
152+
this.syncOptions = [airgapwallet, sparrowwallet]
153+
break
151154
case MainProtocolSymbols.ETH:
152155
case MainProtocolSymbols.OPTIMISM:
153156
this.syncOptions = [airgapwallet]

src/app/pages/address-explorer/address-explorer.page.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { AirGapMarketWallet, MainProtocolSymbols } from '@airgap/coinlib-core'
22
import { Component, OnInit } from '@angular/core'
33
import { NavigationService } from 'src/app/services/navigation/navigation.service'
4-
import * as bip32 from 'bip32'
4+
import { BIP32Factory } from 'bip32'
5+
import * as ecc from '@bitcoinerlab/secp256k1'
56
import * as bs58check from 'bs58check'
67

78
// https://github.com/satoshilabs/slips/blob/master/slip-0132.md
@@ -41,6 +42,8 @@ interface AddressInfo {
4142
styleUrls: ['./address-explorer.page.scss']
4243
})
4344
export class AddressExplorerPage implements OnInit {
45+
private readonly bip32 = BIP32Factory(ecc)
46+
4447
public wallet: AirGapMarketWallet
4548
// public fingerprint: string = ''
4649
public selectedTab: string = 'external'
@@ -56,7 +59,7 @@ export class AddressExplorerPage implements OnInit {
5659
async ngOnInit() {
5760
if (this.wallet) {
5861
// this.fingerprint = this.wallet.masterFingerprint
59-
this.xpub = bip32.fromBase58(new ExtendedPublicKey(this.wallet.publicKey).toXpub()).toBase58()
62+
this.xpub = this.bip32.fromBase58(new ExtendedPublicKey(this.wallet.publicKey).toXpub()).toBase58()
6063
this.clearAddresses()
6164

6265
if ((await this.wallet.protocol.getIdentifier()) === MainProtocolSymbols.ETH) {

src/app/pages/bip85-show/bip85-show.page.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Component } from '@angular/core'
2-
import { BIP32Interface, fromSeed } from 'bip32'
2+
import { BIP32Factory, BIP32Interface } from 'bip32'
3+
import * as ecc from '@bitcoinerlab/secp256k1'
34
import { mnemonicToSeed } from 'bip39'
45
import { Observable } from 'rxjs'
56
import { map } from 'rxjs/operators'
@@ -22,6 +23,7 @@ export class Bip85ShowPage {
2223
private secret: MnemonicSecret
2324
public mnemonicLength: 12 | 18 | 24
2425
public index: number
26+
private readonly bip32 = BIP32Factory(ecc)
2527

2628
public childMnemonic: string | undefined
2729
public childFingerprint: string | undefined
@@ -99,8 +101,8 @@ export class Bip85ShowPage {
99101
this.childMnemonic = childEntropy.toMnemonic()
100102

101103
const seed: Buffer = await mnemonicToSeed(this.childMnemonic)
102-
const bip32Node: BIP32Interface = fromSeed(seed)
103-
this.childFingerprint = bip32Node.fingerprint.toString('hex')
104+
const bip32Node: BIP32Interface = this.bip32.fromSeed(new Uint8Array(seed))
105+
this.childFingerprint = Buffer.from(bip32Node.fingerprint).toString('hex')
104106

105107
this.lifehashData = await this.lifehashService.generateLifehash(this.childFingerprint)
106108
} catch (error) {

src/app/services/iac/iac.service.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,19 @@ export class IACService extends BaseIACService {
155155
// This can happen if we work with third party wallets that have a different format that doesn't include the public key.
156156

157157
// BTC: First we try to find a wallet by matching the masterFingerprint
158-
if (!correctWallet && signTransactionRequest.protocol === MainProtocolSymbols.BTC_SEGWIT) {
158+
if (
159+
!correctWallet &&
160+
(signTransactionRequest.protocol === MainProtocolSymbols.BTC_SEGWIT ||
161+
signTransactionRequest.protocol === MainProtocolSymbols.BTC_TAPROOT)
162+
) {
159163
const transaction: BitcoinSegwitTransactionSignRequest['transaction'] = unsignedTransaction.transaction
160164
const decodedPSBT = bitcoinJS.Psbt.fromHex(transaction.psbt)
165+
const isTaproot = decodedPSBT.data.inputs.some((input) => input.tapBip32Derivation)
166+
161167
for (const input of decodedPSBT.data.inputs) {
162-
for (const derivation of input.bip32Derivation) {
168+
const path = isTaproot ? input.tapBip32Derivation : input.bip32Derivation
169+
170+
for (const derivation of path) {
163171
const masterFingerprint = derivation.masterFingerprint.toString('hex')
164172

165173
correctWallet = await this.secretsService.findWalletByFingerprintDerivationPathAndProtocolIdentifier(
@@ -184,7 +192,7 @@ export class IACService extends BaseIACService {
184192
// Start account selection
185193
const modal = await this.modalController.create({
186194
component: SelectAccountPage,
187-
componentProps: { type: 'psbt', symbolFilter: MainProtocolSymbols.BTC_SEGWIT }
195+
componentProps: { type: 'psbt', symbolFilter: signTransactionRequest.protocol }
188196
})
189197

190198
modal

src/app/services/migration/migration.service.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AirGapWallet, AirGapWalletStatus } from '@airgap/coinlib-core'
22
import { Injectable } from '@angular/core'
3-
import { BIP32Interface, fromSeed } from 'bip32'
3+
import { BIP32Factory, BIP32Interface } from 'bip32'
4+
import * as ecc from '@bitcoinerlab/secp256k1'
45
import { entropyToMnemonic, mnemonicToSeed } from 'bip39'
56

67
import { MnemonicSecret } from '../../models/secret'
@@ -13,6 +14,7 @@ import { SecretsService } from '../secrets/secrets.service'
1314
providedIn: 'root'
1415
})
1516
export class MigrationService {
17+
private readonly bip32 = BIP32Factory(ecc)
1618
constructor(private readonly secretsService: SecretsService, private readonly navigationService: NavigationService) {}
1719

1820
public async runSecretsMigration(secrets: MnemonicSecret[]): Promise<void> {
@@ -74,21 +76,23 @@ export class MigrationService {
7476
return [secrets, true]
7577
}
7678

77-
const migratedSecrets: (MnemonicSecret | undefined)[] = await Promise.all(secrets.map(async (secret: MnemonicSecret) => {
78-
if (!secret.fingerprint) {
79-
return undefined
80-
}
79+
const migratedSecrets: (MnemonicSecret | undefined)[] = await Promise.all(
80+
secrets.map(async (secret: MnemonicSecret) => {
81+
if (!secret.fingerprint) {
82+
return undefined
83+
}
8184

82-
const [migratedWallets]: [AirGapWallet[], boolean] = this.filterMigratedWallets(secret.wallets)
83-
if (migratedWallets.length === 0) {
84-
return undefined
85-
}
85+
const [migratedWallets]: [AirGapWallet[], boolean] = this.filterMigratedWallets(secret.wallets)
86+
if (migratedWallets.length === 0) {
87+
return undefined
88+
}
8689

87-
const newSecret: MnemonicSecret = MnemonicSecret.init(secret)
88-
newSecret.wallets = migratedWallets
90+
const newSecret: MnemonicSecret = MnemonicSecret.init(secret)
91+
newSecret.wallets = migratedWallets
8992

90-
return newSecret
91-
}))
93+
return newSecret
94+
})
95+
)
9296

9397
// create a new array of migrated secrets with filtered wallets
9498
return [migratedSecrets.filter((secret: MnemonicSecret | undefined) => secret !== undefined), false]
@@ -112,8 +116,8 @@ export class MigrationService {
112116
}
113117

114118
const seed: Buffer = await mnemonicToSeed(mnemonic)
115-
const bip32Node: BIP32Interface = fromSeed(seed)
116-
const fingerprint: string = bip32Node.fingerprint.toString('hex')
119+
const bip32Node: BIP32Interface = this.bip32.fromSeed(new Uint8Array(seed))
120+
const fingerprint: string = Buffer.from(bip32Node.fingerprint).toString('hex')
117121

118122
secret.fingerprint = fingerprint
119123
}
@@ -165,8 +169,8 @@ export class MigrationService {
165169
}
166170

167171
const seed: Buffer = await mnemonicToSeed(mnemonic, resolvedOptions.bip39Passphrase)
168-
const bip32Node: BIP32Interface = fromSeed(seed)
169-
const fingerprint: string = bip32Node.fingerprint.toString('hex')
172+
const bip32Node: BIP32Interface = this.bip32.fromSeed(new Uint8Array(seed))
173+
const fingerprint: string = Buffer.from(bip32Node.fingerprint).toString('hex')
170174

171175
wallet.masterFingerprint = fingerprint
172176
wallet.status = AirGapWalletStatus.ACTIVE

0 commit comments

Comments
 (0)