dash-platform-sdk 1.5.0-dev.4 → 1.5.0-dev.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dash-platform-sdk",
3
- "version": "1.5.0-dev.4",
3
+ "version": "1.5.0-dev.5",
4
4
  "main": "index.js",
5
5
  "description": "Lightweight SDK for accessing Dash Platform blockchain",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  import { HDKey } from '@scure/bip32';
2
2
  import { Network } from '../../types.js';
3
- import { OrchardAddressWASM } from 'pshenmic-dpp';
3
+ import { OrchardAddressWASM, PlatformAddressWASM, PrivateKeyWASM } from 'pshenmic-dpp';
4
4
  /**
5
5
  * Collection of functions to work with private keys and seed phrases
6
6
  *
@@ -65,6 +65,16 @@ export declare class KeyPairController {
65
65
  * @returns {string}
66
66
  */
67
67
  p2pkhAddress(publicKey: Uint8Array, network: Network): string;
68
+ /**
69
+ * Converts a public key to a transparent platform P2PKH address (DIP-18):
70
+ * the canonical `variantByte || Hash160(pubkey)` PlatformAddress.
71
+ *
72
+ * @param publicKey {Uint8Array}
73
+ * @param network {Network}
74
+ *
75
+ * @returns {PlatformAddressWASM}
76
+ */
77
+ platformAddress(publicKey: Uint8Array, network: Network): PlatformAddressWASM;
68
78
  /**
69
79
  * Derives a shielded (Orchard) address from a BIP-39 seed via ZIP-32
70
80
  * (m/32'/coinType'/account').
@@ -89,4 +99,56 @@ export declare class KeyPairController {
89
99
  * @returns {Uint8Array} 32-byte outgoing viewing key
90
100
  */
91
101
  deriveShieldedOutgoingViewingKey(seed: Uint8Array, network: Network, account: number): Uint8Array;
102
+ /**
103
+ * Derives the DIP-17 account-level extended public key (xpub) for the
104
+ * clear-funds key class: m/9'/coinType'/17'/account'/0'. The seed is only
105
+ * needed once per account — the returned xpub derives every address index
106
+ * publicly (see {@link derivePlatformAddressFromXpub}), with no further access
107
+ * to the seed.
108
+ *
109
+ * @param seed {Uint8Array} - BIP-39 seed bytes
110
+ * @param network {Network} - network (selects the SLIP-44 coin type)
111
+ * @param account {number} - DIP-17 account index
112
+ *
113
+ * @returns {Promise<string>} account-level extended public key
114
+ */
115
+ derivePlatformAccountXpub(seed: Uint8Array, network: Network, account: number): Promise<string>;
116
+ /**
117
+ * Derives a transparent platform P2PKH address from a BIP-39 seed via DIP-17
118
+ * (m/9'/coinType'/17'/account'/0'/index).
119
+ *
120
+ * @param seed {Uint8Array} - BIP-39 seed bytes
121
+ * @param network {Network} - network (selects the SLIP-44 coin type)
122
+ * @param account {number} - DIP-17 account index
123
+ * @param index {number} - address index
124
+ *
125
+ * @returns {Promise<PlatformAddressWASM>}
126
+ */
127
+ derivePlatformAddress(seed: Uint8Array, network: Network, account: number, index: number): Promise<PlatformAddressWASM>;
128
+ /**
129
+ * Derives a transparent platform P2PKH address from an account-level xpub (see
130
+ * {@link derivePlatformAccountXpub}). The address index is non-hardened, so
131
+ * public-only derivation reproduces the exact same address as the seed-based
132
+ * path — no seed required.
133
+ *
134
+ * @param xpub {string} - account-level extended public key
135
+ * @param network {Network} - network (selects the extended-key version bytes)
136
+ * @param index {number} - address index
137
+ *
138
+ * @returns {PlatformAddressWASM}
139
+ */
140
+ derivePlatformAddressFromXpub(xpub: string, network: Network, index: number): PlatformAddressWASM;
141
+ /**
142
+ * Derives the private key for a DIP-17 platform address by its index
143
+ * (m/9'/coinType'/17'/account'/0'/index). Used to sign a transfer that spends
144
+ * from that address.
145
+ *
146
+ * @param seed {Uint8Array} - BIP-39 seed bytes
147
+ * @param network {Network} - network (selects the SLIP-44 coin type)
148
+ * @param account {number} - DIP-17 account index
149
+ * @param index {number} - address index
150
+ *
151
+ * @returns {Promise<PrivateKeyWASM>}
152
+ */
153
+ derivePlatformAddressPrivateKey(seed: Uint8Array, network: Network, account: number, index: number): Promise<PrivateKeyWASM>;
92
154
  }
@@ -3,7 +3,7 @@ import mnemonicToSeed from './mnemonicToSeed.js';
3
3
  import deriveChild from './deriveChild.js';
4
4
  import derivePath from './derivePath.js';
5
5
  import { p2pkh } from '@scure/btc-signer';
6
- import { OrchardAddressWASM, orchardOvkFromSeed } from 'pshenmic-dpp';
6
+ import { OrchardAddressWASM, orchardOvkFromSeed, PlatformAddressWASM, PrivateKeyWASM } from 'pshenmic-dpp';
7
7
  const DASH_VERSIONS = {
8
8
  mainnet: { pubKeyHash: 0x4c, scriptHash: 0x10, bech32: 'dc', wif: 0xcc, private: 0x0488ade4, public: 0x0488b21e },
9
9
  testnet: { pubKeyHash: 0x8c, scriptHash: 0x13, bech32: 'dc', wif: 0xef, private: 0x04358394, public: 0x043587cf }
@@ -87,6 +87,20 @@ export class KeyPairController {
87
87
  const P2PKH = p2pkh(publicKey, DASH_VERSIONS[network]);
88
88
  return P2PKH.address;
89
89
  }
90
+ /**
91
+ * Converts a public key to a transparent platform P2PKH address (DIP-18):
92
+ * the canonical `variantByte || Hash160(pubkey)` PlatformAddress.
93
+ *
94
+ * @param publicKey {Uint8Array}
95
+ * @param network {Network}
96
+ *
97
+ * @returns {PlatformAddressWASM}
98
+ */
99
+ platformAddress(publicKey, network) {
100
+ const { hash } = p2pkh(publicKey, DASH_VERSIONS[network]);
101
+ // variant byte 0x00 = P2PKH
102
+ return PlatformAddressWASM.fromBytes(Uint8Array.from([0x00, ...hash]));
103
+ }
90
104
  /**
91
105
  * Derives a shielded (Orchard) address from a BIP-39 seed via ZIP-32
92
106
  * (m/32'/coinType'/account').
@@ -119,4 +133,85 @@ export class KeyPairController {
119
133
  const coinType = network === 'mainnet' ? 5 : 1;
120
134
  return orchardOvkFromSeed(seed, coinType, account);
121
135
  }
136
+ /**
137
+ * Derives the DIP-17 account-level extended public key (xpub) for the
138
+ * clear-funds key class: m/9'/coinType'/17'/account'/0'. The seed is only
139
+ * needed once per account — the returned xpub derives every address index
140
+ * publicly (see {@link derivePlatformAddressFromXpub}), with no further access
141
+ * to the seed.
142
+ *
143
+ * @param seed {Uint8Array} - BIP-39 seed bytes
144
+ * @param network {Network} - network (selects the SLIP-44 coin type)
145
+ * @param account {number} - DIP-17 account index
146
+ *
147
+ * @returns {Promise<string>} account-level extended public key
148
+ */
149
+ async derivePlatformAccountXpub(seed, network, account) {
150
+ const coinType = network === 'mainnet' ? 5 : 1;
151
+ const hdKey = this.seedToHdKey(seed, network);
152
+ const accountNode = await this.derivePath(hdKey, `m/9'/${coinType}'/17'/${account}'/0'`);
153
+ return accountNode.publicExtendedKey;
154
+ }
155
+ /**
156
+ * Derives a transparent platform P2PKH address from a BIP-39 seed via DIP-17
157
+ * (m/9'/coinType'/17'/account'/0'/index).
158
+ *
159
+ * @param seed {Uint8Array} - BIP-39 seed bytes
160
+ * @param network {Network} - network (selects the SLIP-44 coin type)
161
+ * @param account {number} - DIP-17 account index
162
+ * @param index {number} - address index
163
+ *
164
+ * @returns {Promise<PlatformAddressWASM>}
165
+ */
166
+ async derivePlatformAddress(seed, network, account, index) {
167
+ const coinType = network === 'mainnet' ? 5 : 1;
168
+ const hdKey = this.seedToHdKey(seed, network);
169
+ const childNode = await this.derivePath(hdKey, `m/9'/${coinType}'/17'/${account}'/0'/${index}`);
170
+ if (childNode.publicKey == null) {
171
+ throw new Error(`Could not derive platform address public key at index ${index}`);
172
+ }
173
+ return this.platformAddress(childNode.publicKey, network);
174
+ }
175
+ /**
176
+ * Derives a transparent platform P2PKH address from an account-level xpub (see
177
+ * {@link derivePlatformAccountXpub}). The address index is non-hardened, so
178
+ * public-only derivation reproduces the exact same address as the seed-based
179
+ * path — no seed required.
180
+ *
181
+ * @param xpub {string} - account-level extended public key
182
+ * @param network {Network} - network (selects the extended-key version bytes)
183
+ * @param index {number} - address index
184
+ *
185
+ * @returns {PlatformAddressWASM}
186
+ */
187
+ derivePlatformAddressFromXpub(xpub, network, index) {
188
+ const accountNode = HDKey.fromExtendedKey(xpub, DASH_VERSIONS[network]);
189
+ const childNode = accountNode.deriveChild(index);
190
+ if (childNode.publicKey == null) {
191
+ throw new Error(`Could not derive platform address public key at index ${index}`);
192
+ }
193
+ return this.platformAddress(childNode.publicKey, network);
194
+ }
195
+ /**
196
+ * Derives the private key for a DIP-17 platform address by its index
197
+ * (m/9'/coinType'/17'/account'/0'/index). Used to sign a transfer that spends
198
+ * from that address.
199
+ *
200
+ * @param seed {Uint8Array} - BIP-39 seed bytes
201
+ * @param network {Network} - network (selects the SLIP-44 coin type)
202
+ * @param account {number} - DIP-17 account index
203
+ * @param index {number} - address index
204
+ *
205
+ * @returns {Promise<PrivateKeyWASM>}
206
+ */
207
+ async derivePlatformAddressPrivateKey(seed, network, account, index) {
208
+ const coinType = network === 'mainnet' ? 5 : 1;
209
+ const hdKey = this.seedToHdKey(seed, network);
210
+ const path = `m/9'/${coinType}'/17'/${account}'/0'/${index}`;
211
+ const { privateKey } = await this.derivePath(hdKey, path);
212
+ if (privateKey == null) {
213
+ throw new Error(`Could not derive platform address key at ${path}`);
214
+ }
215
+ return PrivateKeyWASM.fromBytes(privateKey, network);
216
+ }
122
217
  }
@@ -1,4 +1,4 @@
1
- import { OrchardAddressWASM } from 'pshenmic-dpp';
1
+ import { OrchardAddressWASM, PlatformAddressWASM } from 'pshenmic-dpp';
2
2
  import { DashPlatformSDK } from '../../src/DashPlatformSDK.js';
3
3
  let sdk;
4
4
  let mnemonic;
@@ -63,4 +63,52 @@ describe('KeyPair', () => {
63
63
  expect(ovk).toEqual(sdk.keyPair.deriveShieldedOutgoingViewingKey(seed, 'testnet', account));
64
64
  });
65
65
  });
66
+ describe('platform addresses', () => {
67
+ const account = 0;
68
+ // Reference values produced by the dash-platform-extension derivation utils
69
+ // (proven byte-identical to on-chain / desktop-wallet addresses) for the
70
+ // fixed mnemonic above, testnet, account 0.
71
+ const expectedXpub = 'tpubDH6WaqMU1YHJMbPG413my6Dx5DwnsuuL6EzbetEZQac6ixmkByzCGgiibue3HQmHX5JGVUm2fBHKGttJpVkx3NrK5Em9br8GyLd1fVGm7ud';
72
+ const expectedAddresses = [
73
+ 'tdash1kr0xt5wj85ht5u464rfysjrq75rewz9mysjwf59p',
74
+ 'tdash1kqgy7ngm2wf0zsv20k4mc62s5rapw26yeg6em4jq',
75
+ 'tdash1kzlatzl0u06uxrqz8hkc7naz9d2g3v8g7gw83ew3'
76
+ ];
77
+ test('derives the DIP-17 account xpub matching the reference', async () => {
78
+ const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
79
+ const xpub = await sdk.keyPair.derivePlatformAccountXpub(seed, 'testnet', account);
80
+ expect(xpub).toEqual(expectedXpub);
81
+ });
82
+ test('derives seed-based addresses matching the reference', async () => {
83
+ const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
84
+ for (let index = 0; index < expectedAddresses.length; index++) {
85
+ const address = await sdk.keyPair.derivePlatformAddress(seed, 'testnet', account, index);
86
+ expect(address).toEqual(expect.any(PlatformAddressWASM));
87
+ expect(address.isP2PKH()).toBe(true);
88
+ expect(address.toBech32m('testnet')).toEqual(expectedAddresses[index]);
89
+ }
90
+ });
91
+ test('xpub-based public derivation reproduces the seed-based addresses', async () => {
92
+ const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
93
+ const xpub = await sdk.keyPair.derivePlatformAccountXpub(seed, 'testnet', account);
94
+ for (let index = 0; index < expectedAddresses.length; index++) {
95
+ const fromXpub = sdk.keyPair.derivePlatformAddressFromXpub(xpub, 'testnet', index);
96
+ expect(fromXpub.toBech32m('testnet')).toEqual(expectedAddresses[index]);
97
+ }
98
+ });
99
+ test('private key derivation yields the same pubkey hash as the address', async () => {
100
+ const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
101
+ for (let index = 0; index < expectedAddresses.length; index++) {
102
+ const privateKey = await sdk.keyPair.derivePlatformAddressPrivateKey(seed, 'testnet', account, index);
103
+ const address = await sdk.keyPair.derivePlatformAddress(seed, 'testnet', account, index);
104
+ expect(privateKey.getPublicKeyHash()).toEqual(Buffer.from(address.hash()).toString('hex'));
105
+ }
106
+ });
107
+ test('derives distinct addresses per network', async () => {
108
+ const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
109
+ const mainnet = await sdk.keyPair.derivePlatformAddress(seed, 'mainnet', account, 0);
110
+ const testnet = await sdk.keyPair.derivePlatformAddress(seed, 'testnet', account, 0);
111
+ expect(mainnet.bytes()).not.toEqual(testnet.bytes());
112
+ });
113
+ });
66
114
  });