vacuumlabs/ledgerjs-cardano-shelley

Generating address from ExtendedPublicKey

Closed this issue · 4 comments

Hi,
Is it possible to generate address directly from the ExtendedPublicKey you get from appAda.getExtendedPublicKey()?
I mean, instead of querying Ledger for new address multiple times, can Ledger be queried for the ExtendedPublicKey, and then addresses derived from that without having to query the ledger.

appAda.getExtendedPublicKey().deriveAddress() -> utils.bech32_encodeAddress()

Thanks in advance.

Hi @Zireael, this library is intended just as a thin wrapper over Ledger's calls, nevertheless, you can obtain the account public key (1852'/1815'/<account index>' for Shelley or 44'/1815'/<account index>' for Byron addresses), derive the respective child public keys which involves only non-hardened derivation (i.e. no knowledge of parent private key required) and assemble the addresses without further interaction with Ledger.

You can use e.g. cardano-serialization-lib for that https://github.com/Emurgo/cardano-serialization-lib/blob/master/doc/getting-started/generating-keys.md

Thanks, I got the cardano-serialization working and with ledgerjs-cardano-shelley am able to export the public key in two parts:

{
  publicKeyHex: 'e6cb70c56fffffffffffffffffffffffffffffffffffffffffffffffffff',
  chainCodeHex: '1fadcf740ffffffffffffffffffffffffffffffffffffffffffffffffffff'
}

However cardano-serialization expects the public key in the format 'xpub6DLjthA5JrEnopfffffffffffffffffffffffffffffffffff' and I can't figure out how to do the conversion.
From what I gather, public key is 64-byte: the 32-byte ED25519 public key plus the 32-byte chain code of the public key. How do I construct that?
"xpub" + publicKeyHex + chainCodeHex ?

Something like this?

const rootKey = CardanoWASM.Bip32PublicKey.from_bytes(
  Buffer.from(
    "e6cb70c56fffffffffffffffffffffffffffffffffffffffffffffffffff" +
      "1fadcf740ffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "hex"
  )
);

I checked in runkit and the extended public key as a buffer seems to work fine:

const csl = require("@emurgo/cardano-serialization-lib-nodejs")

const rootKey = csl.Bip32PublicKey.from_bytes(
  Buffer.from(
    "e6ab70c569d8c47bef9b0c01b54c1a0c19dfc6d8fef5957a0a68cb78f8fa64a5" +
      "1faacf74053f64e9109d4b36fa9702e1b0e392a06524e6d0d69f5465dcf58d4f",
    "hex"
  )
); // account (m/1852'/1815'/0') key obtained from ledger (presumably)

const paymentKey = rootKey
    .derive(0) // visible address
    .derive(0) // address index 0
    .to_raw_key();

const stakeKey = rootKey
    .derive(2) // staking chain
    .derive(0) // fixed value for staking keys for given account
    .to_raw_key();

const addr = csl.BaseAddress.new(
    1, // mainnet chain
    csl.StakeCredential.from_keyhash(paymentKey.hash()),
    csl.StakeCredential.from_keyhash(stakeKey.hash())
).to_address();

console.log(addr.to_bytes());
console.log(addr.to_bech32());

You can look up up in Yoroi codebase how child keys are derived and addresses are obtained from the keys https://github.com/Emurgo/yoroi-frontend/blob/2fd4541b5745601dcfa36fde56873d67012dcbfa/app/api/ada/lib/cardanoCrypto/plate.js#L75

And you can use then Ledger's deriveAddress call to sanity-check your results.

Disclaimer - I'm not that familiar with cardano-serialization-lib as I use cardano-crypto.js in my projects which has a lower level api, so there may be errors in the implementation above, however the general idea should be right and you can easily sanity-check your results with Ledger.

Thanks, this seems to work ^^