-
Notifications
You must be signed in to change notification settings - Fork 19
Description
Summary
Lace v2 generates different Midnight addresses from the same mnemonic compared to the official @midnight-ntwrk/wallet-sdk-hd package. This prevents wallet interoperability between Lace and other wallets built using the standard Midnight SDK.
Background
I'm developing an Android wallet for Midnight blockchain. To ensure users can restore wallets between Lace and my application, I need to understand Lace's exact key derivation implementation.
Test Case
Mnemonic (BIP-39 standard test vector):
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art
BIP-39 Passphrase: "" (empty string)
Result Comparison
| Implementation | Address |
|---|---|
| Lace v2 | mn_addr_preview15jlkezafp4mju3v7cdh3ywre2y2s3szgpqrkw8p4tzxjqhuaqhlshsa9pv |
| Midnight SDK | mn_addr_preview19kxg8sxrsty37elmm6yd68tuy7prryjst2r48eapf2fdtd8z4gpq8xczf2 |
❌ Addresses do not match
My Implementation (Using Official Midnight SDK)
import { mnemonicToSeed } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english.js';
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
import * as ledger from '@midnight-ntwrk/ledger-v6';
import { MidnightBech32m } from '@midnight-ntwrk/wallet-sdk-address-format';
const mnemonic = "abandon abandon abandon ... art"; // 24 words
const masterSeed = await mnemonicToSeed(mnemonic, '', wordlist); // BIP-39
const hdWalletResult = HDWallet.fromSeed(masterSeed); // BIP-32
const account = hdWalletResult.hdWallet.selectAccount(0);
// Derivation path: m/44'/2400'/0'/0/0 (NightExternal role)
const nightExternalKey = account.selectRole(Roles.NightExternal).deriveKeyAt(0);
const privateKey = nightExternalKey.key;
// Public key and address generation
const privateKeyHex = bytesToHex(privateKey);
const publicKey = ledger.signatureVerifyingKey(privateKeyHex);
const userAddress = ledger.addressFromKey(publicKey);
const addressBuffer = Buffer.from(userAddress, 'hex');
const bech32Address = new MidnightBech32m('addr', 'preview', addressBuffer).asString();
console.log(bech32Address);
// Output: mn_addr_preview19kxg8sxrsty37elmm6yd68tuy7prryjst2r48eapf2fdtd8z4gpq8xczf2
// Expected (from Lace): mn_addr_preview15jlkezafp4mju3v7cdh3ywre2y2s3szgpqrkw8p4tzxjqhuaqhlshsa9pvExhaustive Search Conducted
Tested 400+ addresses with no matches:
- ✅ Accounts: 0-4
- ✅ Roles: NightExternal (0), NightInternal (1), Dust (2), Zswap (3)
- ✅ Indices: 0-19 per role
- ✅ Passphrase: empty string
Decoded Address Comparison
Lace Address:
Type: addr
Network: preview
Data (hex): a4bf6c8ba90d772e459ec36f123879511508c0480807671c35588d205f9d05ff
Length: 32 bytes
Midnight SDK Address:
Type: addr
Network: preview
Data (hex): 2d8c83c0c382c91f67fbde88dd1d7c27823192505a8753e7a14a92d5b4e2aa02
Length: 32 bytes
The underlying public key data is completely different.
Hypothesis
Since Lace is primarily a Cardano wallet (with Midnight support added in v2), I suspect Lace may be using:
- Cardano's CIP-3 or CIP-1852 key derivation instead of standard BIP-32
- Different derivation path than Midnight's
m/44'/2400'/account'/role/index - Cardano-specific cryptographic primitives (Ed25519 instead of secp256k1?)
Questions for Lace Development Team
-
Does Lace v2 use the standard
@midnight-ntwrk/wallet-sdk-hdpackage for Midnight addresses? -
If not, what key derivation method does Lace use?
- CIP-3 (Cardano's Icarus derivation)?
- CIP-1852 (Cardano HD wallets)?
- Custom implementation?
-
What is the exact derivation path Lace uses for Midnight addresses?
- Is it
m/44'/2400'/0'/0/0as per Midnight docs? - Or something else?
- Is it
-
What cryptographic curve does Lace use for Midnight?
- secp256k1 (Midnight standard)?
- Ed25519 (Cardano standard)?
-
Is there technical documentation for Lace v2's Midnight implementation?
Why This Matters
Wallet interoperability is critical for the Midnight ecosystem. Users should be able to:
- ✅ Create a wallet in Lace → Restore in any Midnight wallet
- ✅ Create a wallet in any Midnight wallet → Restore in Lace
Currently, this is not possible due to different address generation methods.
Request
Could the Lace development team either:
- Provide technical documentation on Lace v2's Midnight key derivation
- Point to the relevant source code in this repository
- Confirm if Lace plans to standardize on the official Midnight SDK
This information is essential for building interoperable wallets in the Midnight ecosystem.
Environment
- Lace Version: v2.x (with Midnight Preview support)
- Midnight SDK Versions:
@midnight-ntwrk/wallet-sdk-hd: 3.0.0-beta.9@midnight-ntwrk/ledger-v6: 6.1.0-alpha.6@midnight-ntwrk/wallet-sdk-address-format: 3.0.0-beta.9
- Test Network: Midnight Preview (testnet)
Related Documentation
Thank you for your help! Understanding Lace's implementation will help build a more interoperable Midnight ecosystem.