Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SeedSigner SeedQR format for importing HD seeds #4959

Closed
eliasnaur opened this issue Aug 17, 2022 · 12 comments
Closed

Support SeedSigner SeedQR format for importing HD seeds #4959

eliasnaur opened this issue Aug 17, 2022 · 12 comments
Assignees
Labels

Comments

@eliasnaur
Copy link

I'd like to propose adding SeedQR import support for the Add Wallet->Import Wallet screen. Other than SeedSigner, there's now support in (at least) Blockstream Jade, Specter DYI.

@Overtorment
Copy link
Member

do they have a js implementation?

@eliasnaur
Copy link
Author

I couldn't find a JS implementation.

@Overtorment
Copy link
Member

well, looks like someone will have to write one.
maybe me

@SuperPhatArrow
Copy link

well, looks like someone will have to write one. maybe me

@Overtorment It doesn't need a whole library, just a quick helper function:

const encodeCompactSeedQR = (wallet) => {
  // private key or recovery phrase
  // this assumes you get recovery phrase
  const phrase = wallet.getSecret();
  const data = phrase
    // convert the words into an array
    .split(' ')
    // get the binary value of the index of each word, left padded
    // this assumes wordList is an array of BIP39 words
    .map((word) => wordList.indexOf(word).toString(2).padStart(11, '0'))
    // concatenate the binary sting values together
    .join('')
    // group them into bytes as an array
    .match(/[01]{8}/g)
    // convert from binary string to number[]
    .map((bin) => parseInt(bin, 2))
    // remove the checksum bits to leave 16 bytes for 12 words and
    // 32 bytes for 24 words
    .slice(0, (parseInt(phrase.split(' ').length) * 32) / 3 / 8);
  return [{ data, mode: 'byte' }]
}

// usage  in your code base:
<QRCodeComponent
  isMenuAvailable={false}
  value={encodeCompactSeedQR(wallet)}
  size={qrCodeSize}
  logoSize={70}
/>

I would not put a logo on it though. The point of seedQR is to reduce the number of QR Code modules to make it easier to transcribe your seed to paper or metal so probably set logo to null and ecl to L in your react-native-qrcode-svg props.

To scan compact SeedQR, you will just get bytes which you can add the checksum. You can then prompt the user for their BIP39 passphrase and generate an HD wallet in the usual way from there, that is to say that if the scanned seedQR is 128-bits it's a 12 word seed and if it is 256-bits it's a 24 word seed.

Hope that helps.

@Overtorment
Copy link
Member

well, looks like someone will have to write one. maybe me

@Overtorment It doesn't need a whole library, just a quick helper function:

const encodeCompactSeedQR = (wallet) => {

  // private key or recovery phrase

  // this assumes you get recovery phrase

  const phrase = wallet.getSecret();

  const data = phrase

    // convert the words into an array

    .split(' ')

    // get the binary value of the index of each word, left padded

    // this assumes wordList is an array of BIP39 words

    .map((word) => wordList.indexOf(word).toString(2).padStart(11, '0'))

    // concatenate the binary sting values together

    .join('')

    // group them into bytes as an array

    .match(/[01]{8}/g)

    // convert from binary string to number[]

    .map((bin) => parseInt(bin, 2))

    // remove the checksum bits to leave 16 bytes for 12 words and

    // 32 bytes for 24 words

    .slice(0, (parseInt(phrase.split(' ').length) * 32) / 3 / 8);

  return [{ data, mode: 'byte' }]

}



// usage  in your code base:

<QRCodeComponent

  isMenuAvailable={false}

  value={encodeCompactSeedQR(wallet)}

  size={qrCodeSize}

  logoSize={70}

/>

I would not put a logo on it though. The point of seedQR is to reduce the number of QR Code modules to make it easier to transcribe your seed to paper or metal so probably set logo to null and ecl to L in your react-native-qrcode-svg props.

To scan compact SeedQR, you will just get bytes which you can add the checksum. You can then prompt the user for their BIP39 passphrase and generate an HD wallet in the usual way from there, that is to say that if the scanned seedQR is 128-bits it's a 12 word seed and if it is 256-bits it's a 24 word seed.

Hope that helps.

Great. Now need a decode func, I think it's more important, so backed up stuff can be imported into a hot wallet

@SuperPhatArrow
Copy link

Sure, I'll take a look at your QR scanner lib docs and post something later

@kdmukai
Copy link

kdmukai commented Sep 8, 2022

SeedQR is also supported in Sparrow Wallet and I have a PR that's currently being reviewed to add it to Specter Desktop. It's also in the Foundation Device's Passport to-do list.

The conversation here thus far has been around the more difficult Compact SeedQR format, but don't forget about the much easier Standard SeedQR format, which is just the BIP-39 wordlist indices of each word in the mnemonic.

# 12-word seed:
approve fruit lens brass ring actual stool coin doll boss strong rate

# Standard SeedQR digit stream:
008607501025021714880023171503630517020917211425

vector6_standard_12word

@kdmukai
Copy link

kdmukai commented Sep 8, 2022

Sure, I'll take a look at your QR scanner lib docs and post something later

My Specter Desktop PR is limited to Standard SeedQR only at the moment as the Specter QR decoder can't handle the binary format. So your work here could be a two-for-one if we leverage it in Specter Desktop, too.

@Overtorment
Copy link
Member

well, compact qr would be more interesing

@SuperPhatArrow
Copy link

SuperPhatArrow commented Sep 8, 2022

After doing a bit of digging through the code, it looks like BW is using react-native-camera which has been deprecated for about a year.

Best bet might be to see what is in ret argument in the onBarCodeRead function. Currently you are only using ret.data but there should be another property of the object called ret.dataRaw

I realise that this is not the right place to put a scan for seedQR as it is a scan to pay someone, but I wanted to find an example of where scanning was used in the app. How the UX is done for that is up to you :)

According to this dataRaw should be a string and I have looked through some old issues that have what looks like hex in that field. For now I will assume it is hex when scanning a binary QR and so I'll continue as though it is, with no way to test it. If it is something else, let me know, and I will try again.

So firstly, in any relevant wallet class, add a new method:

  generateFromCompactSeedQR(hexString) {
    this.secret = bip39.entropyToMnemonic(hexString);
  }

Then, for example, in /screen/wallets/add.js Line 148ish where a user has created a wallet without entropy, before generating it for them, maybe prompt the user if they would like to scan a Compact seedQR or generate a brand new wallet - again, you are best placed to know where to add it to the UX.

Next the user scans the Compact SeedQR and you call w.generateFromCompactSeedQR(ret.rawData)

If dataRaw does return hex, it's as simple as that.

To support standard SeedQR you will get the index numbers, get the words from the indices and call bip39.fromMnemonic(words) - EDIT: the secret is the words so it would just be this.secret = words

@SuperPhatArrow
Copy link

SuperPhatArrow commented Sep 8, 2022

maybe even allow for both kinds in the method...

generateFromSeedQR(qrString) {
  try {
    // compact seedQR should be between 32 - 64 chars long in hex format
    if (qrString.length === 64 || qrString.length === 32) {
      this.secret = bip39.entropyToMnemonic(hexString);
    } else if (qrString.length === 96 || qrString.length === 48) { // standard seedQR
      const words = qrString.match(/[\d]{4}/g)
        .map(num => wordList[parseInt(num)])
        .join(' ');
      this.secret = words;
    } else throw new Error('invalid QR');
  } catch(e) {
  // do something with e
  }
}

@bota87
Copy link

bota87 commented Oct 30, 2023

I'm trying to import this test QR codes but only the standard version works, the compact one isn't read

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants