You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
flowchart LR
A["Wallet mnemonic"]
B["SLIP-39 split"]
C["Printed shares"]
D["Wallet create"]
E["Recovered mnemonic"]
A -- "show --slip-39" --> B
B -- "threshold shares" --> C
C -- "create --from-slip-39" --> D
D -- "reconstructs" --> E
Here are some key observations to aid the review process:
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 Security concerns
Command-line secret exposure: SLIP-39 shares are wallet recovery secrets, and the new wallet create --from-slip-39 --share ... flow accepts them directly as CLI arguments. This can leak shares through shell history and process inspection on multi-user systems.
--share requires SLIP-39 recovery shares to be passed as command-line arguments. In normal shells these can be captured in shell history, process listings, terminal scrollback, or CI logs, which can expose enough material to recover a wallet. Consider accepting shares via an interactive prompt or stdin instead.
.option('--from-slip-39', 'Create a wallet from SLIP-39 shares', false)
.option('--share <share>', 'SLIP-39 share, can be specified multiple times', (value: string, previous: string[]) => {
const list = Array.isArray(previous) ? previous : []
list.push(value)
return list
}, [] as string[])
iterationExponent is read from untrusted share text and directly controls the PBKDF2 work factor during decrypt. Reject unsupported or unexpectedly high exponents before doing expensive recovery work to prevent a crafted share from causing a CPU denial of service.
const first = shares[0]
if (first.groupThreshold !== 1 || first.groupCount !== 1 || first.groupIndex !== 0) {
throw new Error('Only single-group SLIP-39 shares are supported')
}
+if (first.iterationExponent > ITERATION_EXPONENT) {+ throw new Error('Unsupported SLIP-39 iteration exponent')+}
for (const share of shares) {
Suggestion importance[1-10]: 8
__
Why: iterationExponent comes from untrusted SLIP-39 input and can significantly increase pbkdf2Sync cost during decrypt, creating a plausible CPU denial-of-service risk. Bounding it before recovery is accurate and security-relevant, though it may intentionally limit compatibility with higher-exponent shares.
Medium
Possible issue
Avoid shared option mutation
Avoid mutating the previous array because Commander may reuse the default option value across parses in the same process. Return a new array so --share values cannot leak between invocations or tests.
.option('--share <share>', 'SLIP-39 share, can be specified multiple times', (value: string, previous: string[]) => {
- const list = Array.isArray(previous) ? previous : []- list.push(value)- return list+ return [...(Array.isArray(previous) ? previous : []), value]
}, [] as string[])
Suggestion importance[1-10]: 6
__
Why: The --share option parser mutates the previous array, which can cause values to persist across parses if Commander reuses the default array. Returning a new array is a small but valid robustness improvement for CLI tests and repeated invocations.
Low
General
Reject conflicting duplicate shares
uniqueShares silently ignores duplicate share indices even if the duplicate has different share data. Reject conflicting duplicates so tampered or mistyped shares are not hidden by whichever copy appears first.
function uniqueShares(shares: DecodedShare[]): DecodedShare[] {
- const seen = new Set<number>()- const unique: DecodedShare[] = []+ const byIndex = new Map<number, DecodedShare>()
for (const share of shares) {
- if (!seen.has(share.index)) {- seen.add(share.index)- unique.push(share)+ const existing = byIndex.get(share.index)+ if (existing !== undefined) {+ if (!existing.value.equals(share.value)) {+ throw new Error('Conflicting SLIP-39 shares with the same index')+ }+ continue
}
+ byIndex.set(share.index, share)
}
- return unique+ return [...byIndex.values()]
}
Suggestion importance[1-10]: 5
__
Why: uniqueShares currently ignores duplicate indices even when their value differs, which can hide user mistakes or tampered shares. Rejecting conflicting duplicates improves validation clarity, but it is not likely to affect normal successful recovery paths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Type
Enhancement, Tests
Description
Add SLIP-39 wallet sharing
Restore wallets from shares
Validate CLI option combinations
Cover unit and E2E recovery
Diagram Walkthrough
File Walkthrough
wallet-lifecycle.test.ts
Add E2E SLIP-39 wallet recovery coveragetests/e2e/wallet-lifecycle.test.ts
extractSlip39Shareshelper for CLI output.wallet show --slip-39emits shares.slip39.test.ts
Test SLIP-39 split and recovery helperstests/slip39.test.ts
create.mts
Restore wallets from SLIP-39 sharessrc/cli/commands/wallet/create.mts
--from-slip-39wallet creation mode.--shareoptions.show.mts
Display SLIP-39 backup sharessrc/cli/commands/wallet/show.mts
--slip-39,--threshold, and--shares.slip39-wordlist.mts
Add SLIP-39 wordlistsrc/crypto/slip39-wordlist.mts
SLIP39_WORDSfor share encoding.slip39.mts
Implement SLIP-39 crypto primitivessrc/crypto/slip39.mts