Cardano with SLIP-0039

I wrote a specification describing a new method of deriving the master node of the key tree in Cardano hierarchical deterministic wallets. This new method is primarily intended for use with SLIP-0039 (Shamir’s Secret-Sharing for Mnemonic Codes). See https://github.com/satoshilabs/slips/blob/andrewkozlik/slip-0023/slip-0023.md. I encourage you to comment in the pull request https://github.com/satoshilabs/slips/pull/712.

2 Likes

Just an FYI that both the v1 (Daedalus) scheme and the v2 (Icarus) scheme will be deprecated for Shelley in favor of a v3 scheme that uses bech32. The Rust team initiated the migration and the Haskell team wrote a bech32 library for Haskell to match it https://input-output-hk.github.io/cardano-wallet/haddock/edge/bech32-2019.5.8/Codec-Binary-Bech32.html

3 Likes

Does this mean that v3 wallet GUIDs will look different? Similar to how v1 and v2 look different from each other?

Do you mean the addresses? Yes they will look totally different and every existing Yoroi and Daedalus user will have to upgrade their wallets.

1 Like

What do you mean by GUIDs? The generated addresses, keypairs?
As, there is no such terminology in crypto space.
Adrreses:

  • v1: Ddz like addresses
  • v2: Ae2 ones and
  • v3 “ca1”, “ta1” ones.

For the general public, for some clarification:
Bech32 is a new address format (first introduced in BIP173), which will be used in Shelley for example creating bech32 formatted addresses; from a new serialised format which was developed by the rust-team to distinguish between the different address types (Single, Account, Group and Multisig addresses); keys and signatures.

Example HRPs (human readable parts) for different addresses, keys and signatures:

  • “ca” or “ta” (mainnet or testnet addresses generated from public keys),
  • “xprv”, “xpub”, “xsig” (BIP32 HDWallet keys and signatures),
  • “ed25519_sk”, “ed25519_pk” and so on.

For key generation they used BIP32-ED25519, but now they introduced some others for different type of keys.

For address derivation currently they use BIP32 (BIP44 to be more precise e.g. "m/44’/1815’/0’, an account’s extended key), and I highly recommend they should stick on it for HD wallets, cos they will step into the same shoes as Serokell did, which caused a complete rewrite of the legacy code (be a little bit diplomatic).
Based on the above, I would highly recommend to use some general keytree derivation for HD walletts similar to SLIP10.

3 Likes

Here is the spec the Rust team made https://github.com/input-output-hk/implementation-decisions/blob/master/text/0001-address.md

2 Likes

They’ve added Account and Multisig address kinds recently, which are not in the doc you’ve linked.

pub const ADDR_KIND_ACCOUNT: u8 = 0x5;
pub const ADDR_KIND_MULTISIG: u8 = 0x6;
1 Like

Blockquote Yes they will look totally different

Thanks @SebastienGllmt

I played /w the SLIP-0039 a bit, and created a dart implementation of it that uses some BIP44 like derivation for getting the generated mnemonics. See, details below:

import 'package:slip39/slip39dart';

int main() {
  // threshold (N) number of group shares required to reconstruct the master secret.
  final threshold = 2;
  final masterSecret = "ABCDEFGHIJKLMNOP";
  final passphrase = "TREZOR";

  // 4 groups shares and 2 are required to reconstruct the master secret.
  final groups = [
    // Alice group shares. 1 is enough to reconstruct a group share,
    // therefore she needs at least two group shares to be reconstructed,
    [1, 1],
    [1, 1],
    // 3 of 5 Friends' shares are required to reconstruct this group share
    [3, 5],
    // 2 of 6 Family's shares are required to reconstruct this group share
    [2, 6]
  ];

  final slip = Slip39.fromArray(
    masterSecret: masterSecret,
    passphrase: passphrase,
    threshold: threshold,
    groups: groups);

  // One of Alice's share
  final aliceShare = slip.fromPath('r/0').mnemonics;

  // and any two of family's shares.
  var familyShares = slip.fromPath('r/3/1').mnemonics;
  familyShares = familyShares..addAll(slip.fromPath('r/3/3').mnemonics);

  final allShares = aliceShare..addAll(familyShares);

  print("Shares used for restoring the master secret:");
  allShares..forEach((s) => print(s));

  final recoveredSecret = Slip39.recoverSecret(allShares, passphrase);
  assert(masterSecret == recoveredSecret);
  print("Recovered secret: $recoveredSecret");
}
5 Likes

I just like seeing you are still looking at things cardano, things i still do not understand.
Keep looking man, I can make a few connections but honestly am totally lost on what you are looking at.

1 Like

Really cool! I’d love if somebody could help us get this feature into Yoroi since it’d be really nice for a wallet in the ecosystem to support SLIP 39 but I don’t have bandwidth to work on this for a while.

1 Like

I cannot promise anything as I can only code in my free time, which is very limited.:), but was already thinking of implementing it in javascript especially for Node.js what you guys could use in Yoroi (/w browserify or similar).

4 Likes

I have managed to get some free time, though wife was not happy. So, I implemented my dart code in node.js, see the run example below.
But, have not yet uploaded to the github nor published it in npm.org as I need to clean the code and write some unittests.

The app.js e.g. $ cat app.js

const Slip39 = require("./src/slip39.js")
const assert = require('assert')
// threshold (N) number of group shares required to reconstruct the master secret.
var threshold = 2
var masterSecret = "ABCDEFGHIJKLMNOP".encodeHex();
var passphrase = "TREZOR"

// 4 groups shares and 2 are required to reconstruct the master secret.
var groups = [
    // Alice group shares. 1 is enough to reconstruct a group share,
    // therefore she needs at least two group shares to be reconstructed,
    [1, 1],
    [1, 1],
    // 3 of 5 Friends' shares are required to reconstruct this group share
    [3, 5],
    // 2 of 6 Family's shares are required to reconstruct this group share
    [2, 6]
]

var slip = Slip39.fromArray({
    masterSecret: masterSecret,
    passphrase: passphrase,
    threshold: threshold,
    groups: groups
})

// One of Alice's share
var aliceShare = slip.fromPath('r/0').mnemonics

// and any two of family's shares.
var familyShares = slip.fromPath('r/3/1').mnemonics
familyShares = familyShares.concat(slip.fromPath('r/3/3').mnemonics)

var allShares = aliceShare.concat(familyShares)

console.log("Shares used for restoring the master secret:")
allShares.forEach((s) => console.log(s))

var recoveredSecret = Slip39.recoverSecret(allShares, passphrase)
console.log("Master secret: " + masterSecret.decodeHex())
console.log("Recovered one: " + recoveredSecret.decodeHex())
assert(masterSecret.decodeHex() === recoveredSecret.decodeHex())

and the output

ilap@nyx:~/Projects/sandbox/slip39-js$ node app.js 
Shares used for restoring the master secret:
clay merchant acrobat romp birthday have group trouble indicate soldier blimp secret fiber chemical benefit practice garden mental kitchen welcome
clay merchant decision scared dwarf lunar texture flea parking regular gray gray space home fangs indicate acquire herd unfold class
clay merchant decision sister ambition silent mama satisfy dough exceed fiction ecology genuine downtown axle vampire tidy agency garlic involve
Master secret: ABCDEFGHIJKLMNOP
Recovered one: ABCDEFGHIJKLMNOP
5 Likes

Good work @_ilap! Please let me know, when you are finished with the unit tests and test vectors. I would like to add a link to your implementations then.

For anyone who has a Trezor T, you may be interested to know that we just released new firmware for beta testing, which implements a basic version of SLIP-0039 without the groups feature. It is available via Trezor Beta Wallet at https://beta-wallet.trezor.io/. There is a step-by-step guide on our Wiki: https://wiki.trezor.io/Shamir_Backup.

2 Likes

Hi @andrewkozlik,
The javascript version is nearly done, but due to the nature of the package, I need to write some more sophisticated unit tests.

Anyway, I am going to publish it in npmjs tomorrow.

 $ git clone https://github.com/ilap/slip39-js && cd slip39-js
 $ npm install
 $ npm test

  Basic Tests
    Test threshold 1 with 5 of 7 shares of a group combinations
      ✓ Test combination 0 1 2 3 4.
      ✓ Test combination 0 1 2 3 5.
      ✓ Test combination 0 1 2 3 6.
      ✓ Test combination 0 1 2 4 5.
      ✓ Test combination 0 1 2 4 6.
      ✓ Test combination 0 1 2 5 6.
      ✓ Test combination 0 1 3 4 5.
      ✓ Test combination 0 1 3 4 6.
      ✓ Test combination 0 1 3 5 6.
      ✓ Test combination 0 1 4 5 6.
      ✓ Test combination 0 2 3 4 5.
      ✓ Test combination 0 2 3 4 6.
      ✓ Test combination 0 2 3 5 6.
      ✓ Test combination 0 2 4 5 6.
      ✓ Test combination 0 3 4 5 6.
      ✓ Test combination 1 2 3 4 5.
      ✓ Test combination 1 2 3 4 6.
      ✓ Test combination 1 2 3 5 6.
      ✓ Test combination 1 2 4 5 6.
      ✓ Test combination 1 3 4 5 6.
      ✓ Test combination 2 3 4 5 6.
    Test passhrase
      ✓ should return valid mastersecret when user submits valid passphrse
      ✓ should NOT return valid mastersecret when user submits invalid passphrse
      ✓ should return valid mastersecret when user does not submit passphrse
    Test iteration exponent
      ✓ should return valid mastersecret when user apply valid iteration exponent (44ms)
      ✓ should throw an Error when user submits invalid iteration exponent

  Group Shares Tests
    Test all valid combinations of mnemonics
      ✓ should return the valid mastersecret when valid mnemonics used for recovery
    Original test vectors Tests
      ✓ 1. Valid mnemonic without sharing (128 bits)
      ✓ 2. Mnemonic with invalid checksum (128 bits)
      ✓ 3. Mnemonic with invalid padding (128 bits)
      ✓ 4. Basic sharing 2-of-3 (128 bits)
      ✓ 5. Basic sharing 2-of-3 (128 bits)
      ✓ 6. Mnemonics with different identifiers (128 bits)
      ✓ 7. Mnemonics with different iteration exponents (128 bits)
      ✓ 8. Mnemonics with mismatching group thresholds (128 bits)
      ✓ 9. Mnemonics with mismatching group counts (128 bits)
      ✓ 10. Mnemonics with greater group threshold than group counts (128 bits)
      ✓ 11. Mnemonics with duplicate member indices (128 bits)
      ✓ 12. Mnemonics with mismatching member thresholds (128 bits)
      ✓ 13. Mnemonics giving an invalid digest (128 bits)
      ✓ 14. Insufficient number of groups (128 bits, case 1)
      ✓ 15. Insufficient number of groups (128 bits, case 2)
      ✓ 16. Threshold number of groups, but insufficient number of members in one group (128 bits)
      ✓ 17. Threshold number of groups and members in each group (128 bits, case 1)
      ✓ 18. Threshold number of groups and members in each group (128 bits, case 2)
      ✓ 19. Threshold number of groups and members in each group (128 bits, case 3)
      ✓ 20. Valid mnemonic without sharing (256 bits)
      ✓ 21. Mnemonic with invalid checksum (256 bits)
      ✓ 22. Mnemonic with invalid padding (256 bits)
      ✓ 23. Basic sharing 2-of-3 (256 bits)
      ✓ 24. Basic sharing 2-of-3 (256 bits)
      ✓ 25. Mnemonics with different identifiers (256 bits)
      ✓ 26. Mnemonics with different iteration exponents (256 bits)
      ✓ 27. Mnemonics with mismatching group thresholds (256 bits)
      ✓ 28. Mnemonics with mismatching group counts (256 bits)
      ✓ 29. Mnemonics with greater group threshold than group counts (256 bits)
      ✓ 30. Mnemonics with duplicate member indices (256 bits)
      ✓ 31. Mnemonics with mismatching member thresholds (256 bits)
      ✓ 32. Mnemonics giving an invalid digest (256 bits)
      ✓ 33. Insufficient number of groups (256 bits, case 1)
      ✓ 34. Insufficient number of groups (256 bits, case 2)
      ✓ 35. Threshold number of groups, but insufficient number of members in one group (256 bits)
      ✓ 36. Threshold number of groups and members in each group (256 bits, case 1)
      ✓ 37. Threshold number of groups and members in each group (256 bits, case 2)
      ✓ 38. Threshold number of groups and members in each group (256 bits, case 3)
      ✓ 39. Mnemonic with insufficient length
      ✓ 40. Mnemonic with invalid master secret length
    Invalid Shares
      ✓ Short master secret
      ✓ Odd length master secret
      ✓ Group threshold exceeds number of groups
      ✓ Invalid group threshold.
      ✓ Member threshold exceeds number of members
      ✓ Invalid member threshold
      ✓ Group with multiple members and threshold 1


  74 passing (477ms)
2 Likes

Hi,

Both implementations (dart and javascript) are good enough from now for using them in any project.
I added unit tests, cleaned the code a bit and changed its versioning.

So, the code (v0.1.5-dev.1) is ready now for further testing.

4 Likes

Добрый день. А как я могу пополнить кошелёк yoroi? Зарание спасибо.

1 Like

Добрый день!

Чтобы пополнить кошелек Yoroi:

  1. Перейдите на вкладку «Получить» в Вашем кошельке и сгенерируйте адрес. (Используйте кнопку в пользовательском интерфейсе, чтобы скопировать его в буфер обмена и затем поделиться с пользователем, который собирается отправить Вам Ada или ввести на бирже, где вы будете покупать монеты.)

  2. Если Вы собираетесь купить Ada на крипто-бирже - перейдите туда, инциируйте сделку на покупку, следуя инструкциям биржы и вставьте скопированный адрес с кошелька Yoroi.

  3. Когда сделка будет завершена - вам будет предоставлен ID транзакции по которому вы сможете отслежить состояние на обозревателе блоков, к примеру на https://www.seiza.com/home

1 Like

Огромное спасибо.thank you so much