dash-platform-sdk 1.4.0 → 1.5.0-dev.1
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/README.md +15 -15
- package/bundle.min.js +17 -17
- package/package.json +2 -2
- package/proto/generated/platform.client.d.ts +60 -0
- package/proto/generated/platform.client.js +42 -0
- package/proto/generated/platform.d.ts +783 -0
- package/proto/generated/platform.js +1582 -1
- package/src/DashPlatformSDK.d.ts +2 -0
- package/src/DashPlatformSDK.js +3 -0
- package/src/constants.d.ts +1 -0
- package/src/constants.js +1 -0
- package/src/keyPair/index.d.ts +25 -0
- package/src/keyPair/index.js +33 -0
- package/src/shielded/createStateTransition.d.ts +3 -0
- package/src/shielded/createStateTransition.js +55 -0
- package/src/shielded/getMostRecentShieldedAnchor.d.ts +2 -0
- package/src/shielded/getMostRecentShieldedAnchor.js +36 -0
- package/src/shielded/getShieldedAnchors.d.ts +2 -0
- package/src/shielded/getShieldedAnchors.js +36 -0
- package/src/shielded/getShieldedEncryptedNotes.d.ts +3 -0
- package/src/shielded/getShieldedEncryptedNotes.js +43 -0
- package/src/shielded/getShieldedNotesCount.d.ts +2 -0
- package/src/shielded/getShieldedNotesCount.js +36 -0
- package/src/shielded/getShieldedNullifiers.d.ts +3 -0
- package/src/shielded/getShieldedNullifiers.js +40 -0
- package/src/shielded/getShieldedPoolState.d.ts +2 -0
- package/src/shielded/getShieldedPoolState.js +36 -0
- package/src/shielded/index.d.ts +130 -0
- package/src/shielded/index.js +186 -0
- package/test/unit/KeyPair.spec.js +32 -0
- package/test/unit/Shielded.spec.d.ts +1 -0
- package/test/unit/Shielded.spec.js +236 -0
- package/types.d.ts +85 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import getShieldedEncryptedNotes from './getShieldedEncryptedNotes.js';
|
|
2
|
+
import getShieldedAnchors from './getShieldedAnchors.js';
|
|
3
|
+
import getMostRecentShieldedAnchor from './getMostRecentShieldedAnchor.js';
|
|
4
|
+
import getShieldedPoolState from './getShieldedPoolState.js';
|
|
5
|
+
import getShieldedNotesCount from './getShieldedNotesCount.js';
|
|
6
|
+
import getShieldedNullifiers from './getShieldedNullifiers.js';
|
|
7
|
+
import createStateTransition from './createStateTransition.js';
|
|
8
|
+
import { CommitmentTreeWASM, ContractBoundsWASM, IdentityPublicKeyInCreationWASM, recoverNotes, SerializedActionWASM, ShieldedBuilderWASM, ShieldedMemoWASM, SpendableNoteWASM } from 'pshenmic-dpp';
|
|
9
|
+
/**
|
|
10
|
+
* Shielded controller for requesting information about shielded pool
|
|
11
|
+
*
|
|
12
|
+
* @hideconstructor
|
|
13
|
+
*/
|
|
14
|
+
export class ShieldedController {
|
|
15
|
+
/** @ignore **/
|
|
16
|
+
grpcPool;
|
|
17
|
+
/** @ignore **/
|
|
18
|
+
shieldedBuilder;
|
|
19
|
+
constructor(grpcPool) {
|
|
20
|
+
this.grpcPool = grpcPool;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Set bindings instance of builder
|
|
24
|
+
* Needed for custom builder load, because ShieldedBuilderWASM inits very slow on old hardware
|
|
25
|
+
*/
|
|
26
|
+
init(builder) {
|
|
27
|
+
this.shieldedBuilder = builder ?? new ShieldedBuilderWASM();
|
|
28
|
+
return this.shieldedBuilder;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Lazily constructs and caches a {ShieldedBuilderWASM}. Construction builds
|
|
32
|
+
* the Halo 2 proving key (~seconds), so the instance is created once and reused.
|
|
33
|
+
*
|
|
34
|
+
* @ignore
|
|
35
|
+
*/
|
|
36
|
+
getShieldedBuilder() {
|
|
37
|
+
if (this.shieldedBuilder == null) {
|
|
38
|
+
return this.init();
|
|
39
|
+
}
|
|
40
|
+
return this.shieldedBuilder;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Retrieves a batch of shielded encrypted notes from the shielded pool,
|
|
44
|
+
* starting from the given note index.
|
|
45
|
+
*
|
|
46
|
+
* Returns an array of encrypted notes, each containing the nullifier,
|
|
47
|
+
* note commitment (cmx), encrypted note payload and value commitment (cvNet).
|
|
48
|
+
*
|
|
49
|
+
* @param startIndex {bigint} - index of the first note to fetch
|
|
50
|
+
* @param count {number} - amount of notes to fetch
|
|
51
|
+
*
|
|
52
|
+
* @return {Promise<ShieldedEncryptedNote[]>}
|
|
53
|
+
*/
|
|
54
|
+
async getShieldedEncryptedNotes(startIndex, count) {
|
|
55
|
+
return await getShieldedEncryptedNotes(this.grpcPool, startIndex, count);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Retrieves the set of valid anchors (note commitment tree roots) currently
|
|
59
|
+
* accepted by the shielded pool.
|
|
60
|
+
*
|
|
61
|
+
* @return {Promise<Uint8Array[]>}
|
|
62
|
+
*/
|
|
63
|
+
async getShieldedAnchors() {
|
|
64
|
+
return await getShieldedAnchors(this.grpcPool);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves the most recent anchor (note commitment tree root) of the shielded pool.
|
|
68
|
+
*
|
|
69
|
+
* @return {Promise<Uint8Array>}
|
|
70
|
+
*/
|
|
71
|
+
async getMostRecentShieldedAnchor() {
|
|
72
|
+
return await getMostRecentShieldedAnchor(this.grpcPool);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Retrieves the total balance currently held in the shielded pool.
|
|
76
|
+
*
|
|
77
|
+
* @return {Promise<bigint>}
|
|
78
|
+
*/
|
|
79
|
+
async getShieldedPoolState() {
|
|
80
|
+
return await getShieldedPoolState(this.grpcPool);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Retrieves the total count of notes (leaves) in the shielded notes
|
|
84
|
+
* commitment tree. Useful as a denominator for shielded sync progress.
|
|
85
|
+
*
|
|
86
|
+
* @return {Promise<bigint>}
|
|
87
|
+
*/
|
|
88
|
+
async getShieldedNotesCount() {
|
|
89
|
+
return await getShieldedNotesCount(this.grpcPool);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Checks the spent status of the given nullifiers in the shielded pool.
|
|
93
|
+
*
|
|
94
|
+
* Returns an array of statuses, each containing the nullifier and whether
|
|
95
|
+
* it has already been spent.
|
|
96
|
+
*
|
|
97
|
+
* @param nullifiers {Uint8Array[]} - nullifiers to check
|
|
98
|
+
*
|
|
99
|
+
* @return {Promise<ShieldedNullifierStatus[]>}
|
|
100
|
+
*/
|
|
101
|
+
async getShieldedNullifiers(nullifiers) {
|
|
102
|
+
return await getShieldedNullifiers(this.grpcPool, nullifiers);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Recovers the notes owned by a wallet from a set of shielded encrypted notes
|
|
106
|
+
* (as returned by {@link getShieldedEncryptedNotes}) by trial-decrypting each
|
|
107
|
+
* with the viewing key derived from the seed (ZIP-32 m/32'/coinType'/account').
|
|
108
|
+
* Only notes addressed to the wallet are returned, each with its global leaf
|
|
109
|
+
* position (`index`) in the commitment tree.
|
|
110
|
+
*
|
|
111
|
+
* @param notes {ShieldedEncryptedNote[]} - shielded encrypted notes to scan
|
|
112
|
+
* @param seed {Uint8Array} - BIP-39 seed bytes
|
|
113
|
+
* @param account {number} - ZIP-32 account index
|
|
114
|
+
*
|
|
115
|
+
* @return {RecoveredNoteWASM[]}
|
|
116
|
+
*/
|
|
117
|
+
recoverNotes(notes, seed, account) {
|
|
118
|
+
// SLIP-44 coin type: 5 = Dash mainnet, 1 = testnets. Orchard derives via ZIP-32
|
|
119
|
+
const coinType = this.grpcPool.network === 'mainnet' ? 5 : 1;
|
|
120
|
+
// ShieldedEncryptedNote carries everything needed for trial-decryption; the
|
|
121
|
+
// spend-authorization fields (rk, spendAuthSig) are unused here, so zero them.
|
|
122
|
+
const actions = notes.map(note => new SerializedActionWASM(note.nullifier, new Uint8Array(32), note.cmx, note.encryptedNote, note.cvNet, new Uint8Array(64)));
|
|
123
|
+
return recoverNotes(actions, seed, coinType, account);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Rebuilds the note commitment tree from the full on-chain note set and
|
|
127
|
+
* witnesses the given recovered notes against it, producing the spendable
|
|
128
|
+
* notes and the shared anchor required by spend transitions (`shieldedTransfer`,
|
|
129
|
+
* `unshield`, `shieldedWithdrawal`, `identityCreateFromShieldedPool`).
|
|
130
|
+
*
|
|
131
|
+
* Pass the complete note set from {@link getShieldedEncryptedNotes} as `notes`
|
|
132
|
+
* (the commitment tree leaves) and the notes you want to spend from
|
|
133
|
+
* {@link recoverNotes} as `recovered`. This keeps the whole spend flow inside
|
|
134
|
+
* the SDK — no need to construct a commitment tree yourself.
|
|
135
|
+
*
|
|
136
|
+
* @param notes {ShieldedEncryptedNote[]} - full on-chain note set (the tree leaves)
|
|
137
|
+
* @param recovered {RecoveredNoteWASM[]} - the recovered notes to spend
|
|
138
|
+
*
|
|
139
|
+
* @return {{ spends: SpendableNoteWASM[], anchor: Uint8Array }}
|
|
140
|
+
*/
|
|
141
|
+
buildSpendableNotes(notes, recovered) {
|
|
142
|
+
// only the leaves we intend to witness need to be marked spendable
|
|
143
|
+
const spendableIndices = new Set(recovered.map(note => note.index));
|
|
144
|
+
const tree = new CommitmentTreeWASM();
|
|
145
|
+
notes.forEach((note, index) => tree.append(note.cmx, spendableIndices.has(index)));
|
|
146
|
+
tree.checkpoint(0);
|
|
147
|
+
const anchor = tree.anchor();
|
|
148
|
+
const spends = recovered.map(recoveredNote => {
|
|
149
|
+
const merklePath = tree.witness(recoveredNote.index, 0) ?? tree.witness(recoveredNote.index, 1);
|
|
150
|
+
if (merklePath == null) {
|
|
151
|
+
throw new Error(`Failed to witness recovered note at index ${recoveredNote.index}`);
|
|
152
|
+
}
|
|
153
|
+
return new SpendableNoteWASM(recoveredNote.note, merklePath);
|
|
154
|
+
});
|
|
155
|
+
return { spends, anchor };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Helper function for building and proving shielded (Orchard) transitions.
|
|
159
|
+
* It may be used to create any of 6 shielded transition actions:
|
|
160
|
+
*
|
|
161
|
+
* 1) shield - transparent platform addresses -> pool (deposit)
|
|
162
|
+
* 2) shieldFromAssetLock - asset lock -> pool (deposit)
|
|
163
|
+
* 3) shieldedWithdrawal - pool -> core L1 (spend)
|
|
164
|
+
* 4) unshield - pool -> platform identity balance (spend)
|
|
165
|
+
* 5) shieldedTransfer - pool -> pool (spend)
|
|
166
|
+
* 6) identityCreateFromShieldedPool - pool -> new identity (spend)
|
|
167
|
+
*
|
|
168
|
+
* Spends require a {SpendableNoteWASM} witnessed against an on-chain anchor.
|
|
169
|
+
*
|
|
170
|
+
* @param type {string} Type of the shielded transition, must be one of ('shield' | 'shieldFromAssetLock' | 'shieldedWithdrawal' | 'unshield' | 'shieldedTransfer' | 'identityCreateFromShieldedPool')
|
|
171
|
+
* @param params {ShieldedTransitionParams} params
|
|
172
|
+
*
|
|
173
|
+
* @return {StateTransitionWASM}
|
|
174
|
+
*/
|
|
175
|
+
createStateTransition(type, params) {
|
|
176
|
+
// @ts-expect-error a plain string memo is normalized to ShieldedMemoWASM (empty when omitted)
|
|
177
|
+
params.memo = typeof params.memo === 'string' ? ShieldedMemoWASM.fromString(params.memo) : ShieldedMemoWASM.empty();
|
|
178
|
+
if (type === 'identityCreateFromShieldedPool') {
|
|
179
|
+
const identityParams = params;
|
|
180
|
+
// @ts-expect-error builder expects IdentityPublicKeyInCreationWASM instances
|
|
181
|
+
identityParams.publicKeys = identityParams.publicKeys
|
|
182
|
+
.map(({ id, purpose, securityLevel, keyType, readOnly, data, signature, contractBounds }) => new IdentityPublicKeyInCreationWASM(id, purpose, securityLevel, keyType, readOnly, data, signature, (contractBounds != null) ? new ContractBoundsWASM(contractBounds.dataContractId, contractBounds.documentType) : undefined));
|
|
183
|
+
}
|
|
184
|
+
return createStateTransition(this.getShieldedBuilder(), type, params);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OrchardAddressWASM } from 'pshenmic-dpp';
|
|
1
2
|
import { DashPlatformSDK } from '../../src/DashPlatformSDK.js';
|
|
2
3
|
let sdk;
|
|
3
4
|
let mnemonic;
|
|
@@ -31,4 +32,35 @@ describe('KeyPair', () => {
|
|
|
31
32
|
expect(key.privateKey).toEqual(Uint8Array.from([89, 255, 64, 41, 202, 170, 83, 68, 135, 58, 161, 107, 130, 20, 3, 50, 69, 16, 108, 104, 32, 68, 13, 100, 225, 24, 79, 20, 193, 184, 238, 55]));
|
|
32
33
|
});
|
|
33
34
|
});
|
|
35
|
+
describe('shielded', () => {
|
|
36
|
+
const account = 0;
|
|
37
|
+
test('should be able to derive a shielded address from seed', () => {
|
|
38
|
+
const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
|
|
39
|
+
const address = sdk.keyPair.deriveShieldedAddress(seed, 'testnet', account);
|
|
40
|
+
expect(address).toEqual(expect.any(OrchardAddressWASM));
|
|
41
|
+
expect(address.bytes()).toHaveLength(43);
|
|
42
|
+
// deterministic for the same seed/network/account
|
|
43
|
+
expect(address.bytes()).toEqual(sdk.keyPair.deriveShieldedAddress(seed, 'testnet', account).bytes());
|
|
44
|
+
});
|
|
45
|
+
test('should derive distinct shielded addresses per diversifier index', () => {
|
|
46
|
+
const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
|
|
47
|
+
const first = sdk.keyPair.deriveShieldedAddress(seed, 'testnet', account, 0);
|
|
48
|
+
const second = sdk.keyPair.deriveShieldedAddress(seed, 'testnet', account, 1);
|
|
49
|
+
expect(first.bytes()).not.toEqual(second.bytes());
|
|
50
|
+
});
|
|
51
|
+
test('should derive distinct shielded addresses per network', () => {
|
|
52
|
+
const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
|
|
53
|
+
const mainnet = sdk.keyPair.deriveShieldedAddress(seed, 'mainnet', account);
|
|
54
|
+
const testnet = sdk.keyPair.deriveShieldedAddress(seed, 'testnet', account);
|
|
55
|
+
expect(mainnet.bytes()).not.toEqual(testnet.bytes());
|
|
56
|
+
});
|
|
57
|
+
test('should be able to derive the outgoing viewing key from seed', () => {
|
|
58
|
+
const seed = sdk.keyPair.mnemonicToSeed(mnemonic);
|
|
59
|
+
const ovk = sdk.keyPair.deriveShieldedOutgoingViewingKey(seed, 'testnet', account);
|
|
60
|
+
expect(ovk).toBeInstanceOf(Uint8Array);
|
|
61
|
+
expect(ovk).toHaveLength(32);
|
|
62
|
+
// deterministic for the same seed/network/account
|
|
63
|
+
expect(ovk).toEqual(sdk.keyPair.deriveShieldedOutgoingViewingKey(seed, 'testnet', account));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
34
66
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { AddressFundsFeeStrategyStepWASM, AssetLockProofWASM, CommitmentTreeWASM, CoreScriptWASM, InputAddressWASM, KeyType, NoteWASM, OrchardAddressWASM, OutPointWASM, PlatformAddressWASM, PrivateKeyWASM, Purpose, SecurityLevel, SpendableNoteWASM, StateTransitionWASM } from 'pshenmic-dpp';
|
|
2
|
+
import { DashPlatformSDK } from '../../src/DashPlatformSDK.js';
|
|
3
|
+
let sdk;
|
|
4
|
+
const COIN_TYPE = 1;
|
|
5
|
+
const ACCOUNT = 0;
|
|
6
|
+
function randomBytes(length) {
|
|
7
|
+
const bytes = new Uint8Array(length);
|
|
8
|
+
for (let i = 0; i < length; i++) {
|
|
9
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
10
|
+
}
|
|
11
|
+
return bytes;
|
|
12
|
+
}
|
|
13
|
+
// Builds a 21-byte (version + pubkey hash) platform address.
|
|
14
|
+
function platformAddress(keyHash = randomBytes(20)) {
|
|
15
|
+
const bytes = new Uint8Array(21);
|
|
16
|
+
bytes[0] = 0x00;
|
|
17
|
+
bytes.set(keyHash, 1);
|
|
18
|
+
return PlatformAddressWASM.fromBytes(bytes);
|
|
19
|
+
}
|
|
20
|
+
// Builds a spendable note owned by `seed` and a matching anchor by inserting its
|
|
21
|
+
// commitment into a fresh commitment tree and witnessing it.
|
|
22
|
+
function makeSpendableNote(seed, value) {
|
|
23
|
+
const owner = OrchardAddressWASM.fromSeed(seed, COIN_TYPE, ACCOUNT);
|
|
24
|
+
const rho = randomBytes(32);
|
|
25
|
+
rho[31] &= 0x1f; // keep rho a valid Pallas base field element
|
|
26
|
+
const note = new NoteWASM(owner, value, rho, randomBytes(32));
|
|
27
|
+
const tree = new CommitmentTreeWASM();
|
|
28
|
+
tree.append(note.cmx, true);
|
|
29
|
+
tree.checkpoint(0);
|
|
30
|
+
const anchor = tree.anchor();
|
|
31
|
+
const merklePath = tree.witness(0, 0) ?? tree.witness(0, 1);
|
|
32
|
+
if (merklePath == null) {
|
|
33
|
+
throw new Error('failed to witness note');
|
|
34
|
+
}
|
|
35
|
+
return { spend: new SpendableNoteWASM(note, merklePath), anchor, owner };
|
|
36
|
+
}
|
|
37
|
+
describe('Shielded', () => {
|
|
38
|
+
beforeAll(() => {
|
|
39
|
+
sdk = new DashPlatformSDK({ network: 'testnet' });
|
|
40
|
+
});
|
|
41
|
+
test('getShieldedNotesCount', async () => {
|
|
42
|
+
const count = await sdk.shielded.getShieldedNotesCount();
|
|
43
|
+
expect(typeof count).toBe('bigint');
|
|
44
|
+
});
|
|
45
|
+
test('getShieldedPoolState', async () => {
|
|
46
|
+
const totalBalance = await sdk.shielded.getShieldedPoolState();
|
|
47
|
+
expect(typeof totalBalance).toBe('bigint');
|
|
48
|
+
});
|
|
49
|
+
test('getShieldedAnchors', async () => {
|
|
50
|
+
const anchors = await sdk.shielded.getShieldedAnchors();
|
|
51
|
+
expect(Array.isArray(anchors)).toBe(true);
|
|
52
|
+
for (const anchor of anchors) {
|
|
53
|
+
expect(anchor).toBeInstanceOf(Uint8Array);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
test('getMostRecentShieldedAnchor', async () => {
|
|
57
|
+
const anchor = await sdk.shielded.getMostRecentShieldedAnchor();
|
|
58
|
+
expect(anchor).toBeInstanceOf(Uint8Array);
|
|
59
|
+
});
|
|
60
|
+
test('getShieldedEncryptedNotes', async () => {
|
|
61
|
+
const count = await sdk.shielded.getShieldedNotesCount() ?? 0n;
|
|
62
|
+
const requested = Number(count > 10n ? 10n : count);
|
|
63
|
+
const notes = await sdk.shielded.getShieldedEncryptedNotes(0n, requested);
|
|
64
|
+
expect(notes).toHaveLength(requested);
|
|
65
|
+
for (const note of notes) {
|
|
66
|
+
expect(note.nullifier).toBeInstanceOf(Uint8Array);
|
|
67
|
+
expect(note.cmx).toBeInstanceOf(Uint8Array);
|
|
68
|
+
expect(note.encryptedNote).toBeInstanceOf(Uint8Array);
|
|
69
|
+
expect(note.cvNet).toBeInstanceOf(Uint8Array);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
test('getShieldedNullifiers', async () => {
|
|
73
|
+
const notes = await sdk.shielded.getShieldedEncryptedNotes(0n, 1);
|
|
74
|
+
const nullifiers = notes.map(note => note.nullifier);
|
|
75
|
+
const statuses = await sdk.shielded.getShieldedNullifiers(nullifiers);
|
|
76
|
+
expect(statuses).toHaveLength(nullifiers.length);
|
|
77
|
+
for (const status of statuses) {
|
|
78
|
+
expect(status.nullifier).toBeInstanceOf(Uint8Array);
|
|
79
|
+
expect(typeof status.isSpent).toBe('boolean');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
test('recoverNotes', () => {
|
|
83
|
+
const seed = randomBytes(32);
|
|
84
|
+
const notes = [
|
|
85
|
+
{ nullifier: randomBytes(32), cmx: randomBytes(32), encryptedNote: randomBytes(580), cvNet: randomBytes(32) }
|
|
86
|
+
];
|
|
87
|
+
const recovered = sdk.shielded.recoverNotes(notes, seed, ACCOUNT);
|
|
88
|
+
// random notes are not addressed to this seed, so none are recovered
|
|
89
|
+
expect(Array.isArray(recovered)).toBe(true);
|
|
90
|
+
expect(recovered).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
test('buildSpendableNotes produces a spend usable by a transition', () => {
|
|
93
|
+
const seed = randomBytes(32);
|
|
94
|
+
const owner = OrchardAddressWASM.fromSeed(seed, COIN_TYPE, ACCOUNT);
|
|
95
|
+
const rho = randomBytes(32);
|
|
96
|
+
rho[31] &= 0x1f;
|
|
97
|
+
const note = new NoteWASM(owner, BigInt(10000000000), rho, randomBytes(32));
|
|
98
|
+
// the on-chain note set: only `cmx` matters for rebuilding the tree
|
|
99
|
+
const notes = [
|
|
100
|
+
{ nullifier: randomBytes(32), cmx: note.cmx, encryptedNote: randomBytes(580), cvNet: randomBytes(32) }
|
|
101
|
+
];
|
|
102
|
+
// buildSpendableNotes only reads `.note` and `.index` off a recovered note
|
|
103
|
+
const recovered = [{ note, index: 0 }];
|
|
104
|
+
const { spends, anchor } = sdk.shielded.buildSpendableNotes(notes, recovered);
|
|
105
|
+
expect(spends).toHaveLength(1);
|
|
106
|
+
expect(spends[0]).toEqual(expect.any(SpendableNoteWASM));
|
|
107
|
+
expect(anchor).toBeInstanceOf(Uint8Array);
|
|
108
|
+
// the produced spend + anchor must actually prove a spend transition
|
|
109
|
+
const stateTransition = sdk.shielded.createStateTransition('shieldedTransfer', {
|
|
110
|
+
spends,
|
|
111
|
+
recipient: OrchardAddressWASM.fromSeed(randomBytes(32), COIN_TYPE, ACCOUNT),
|
|
112
|
+
transferAmount: BigInt(1000000),
|
|
113
|
+
changeAddress: owner,
|
|
114
|
+
seed,
|
|
115
|
+
coinType: COIN_TYPE,
|
|
116
|
+
account: ACCOUNT,
|
|
117
|
+
anchor
|
|
118
|
+
});
|
|
119
|
+
expect(stateTransition).toEqual(expect.any(StateTransitionWASM));
|
|
120
|
+
}, 60000);
|
|
121
|
+
describe('should be able to create state transition', () => {
|
|
122
|
+
test('should be able to create a shield transition', () => {
|
|
123
|
+
const seed = randomBytes(32);
|
|
124
|
+
const recipient = OrchardAddressWASM.fromSeed(seed, COIN_TYPE, ACCOUNT);
|
|
125
|
+
const privateKey = new PrivateKeyWASM(randomBytes(32), 'testnet');
|
|
126
|
+
const address = platformAddress(privateKey.getPublicKey().hash160());
|
|
127
|
+
const stateTransition = sdk.shielded.createStateTransition('shield', {
|
|
128
|
+
recipient,
|
|
129
|
+
shieldAmount: BigInt(50000000),
|
|
130
|
+
inputs: [new InputAddressWASM(address, 0, BigInt(100000000))],
|
|
131
|
+
privateKeys: [privateKey],
|
|
132
|
+
feeStrategy: [AddressFundsFeeStrategyStepWASM.DeductFromInput(0)],
|
|
133
|
+
userFeeIncrease: 0
|
|
134
|
+
// memo omitted -> defaults to an empty memo
|
|
135
|
+
});
|
|
136
|
+
expect(stateTransition).toEqual(expect.any(StateTransitionWASM));
|
|
137
|
+
}, 60000);
|
|
138
|
+
test('should be able to create a shieldFromAssetLock transition', () => {
|
|
139
|
+
const seed = randomBytes(32);
|
|
140
|
+
const recipient = OrchardAddressWASM.fromSeed(seed, COIN_TYPE, ACCOUNT);
|
|
141
|
+
const privateKey = PrivateKeyWASM.fromHex('edd04a71bddb31e530f6c2314fd42ada333f6656bb853ece13f0577a8fd30612', 'testnet');
|
|
142
|
+
const txid = '61aede830477254876d435a317241ad46753c4b1350dc991a45ebcf19ab80a11';
|
|
143
|
+
const assetLockProof = AssetLockProofWASM.createChainAssetLockProof(1337, new OutPointWASM(txid, 0));
|
|
144
|
+
const stateTransition = sdk.shielded.createStateTransition('shieldFromAssetLock', {
|
|
145
|
+
recipient,
|
|
146
|
+
shieldAmount: BigInt(50000000),
|
|
147
|
+
assetLockProof,
|
|
148
|
+
privateKey,
|
|
149
|
+
memo: 'shielded-memo-padded-to-32-bytes',
|
|
150
|
+
dummyOutputs: 0
|
|
151
|
+
});
|
|
152
|
+
expect(stateTransition).toEqual(expect.any(StateTransitionWASM));
|
|
153
|
+
}, 60000);
|
|
154
|
+
test('should be able to create a shieldedTransfer transition', () => {
|
|
155
|
+
const seed = randomBytes(32);
|
|
156
|
+
const { spend, anchor, owner } = makeSpendableNote(seed, BigInt(10000000000));
|
|
157
|
+
const recipient = OrchardAddressWASM.fromSeed(randomBytes(32), COIN_TYPE, ACCOUNT);
|
|
158
|
+
const stateTransition = sdk.shielded.createStateTransition('shieldedTransfer', {
|
|
159
|
+
spends: [spend],
|
|
160
|
+
recipient,
|
|
161
|
+
transferAmount: BigInt(1000000),
|
|
162
|
+
changeAddress: owner,
|
|
163
|
+
seed,
|
|
164
|
+
coinType: COIN_TYPE,
|
|
165
|
+
account: ACCOUNT,
|
|
166
|
+
anchor,
|
|
167
|
+
memo: 'shielded-memo-padded-to-32-bytes'
|
|
168
|
+
});
|
|
169
|
+
expect(stateTransition).toEqual(expect.any(StateTransitionWASM));
|
|
170
|
+
}, 60000);
|
|
171
|
+
test('should be able to create an unshield transition', () => {
|
|
172
|
+
const seed = randomBytes(32);
|
|
173
|
+
const { spend, anchor, owner } = makeSpendableNote(seed, BigInt(10000000000));
|
|
174
|
+
const stateTransition = sdk.shielded.createStateTransition('unshield', {
|
|
175
|
+
spends: [spend],
|
|
176
|
+
outputAddress: platformAddress(),
|
|
177
|
+
unshieldAmount: BigInt(1000000),
|
|
178
|
+
changeAddress: owner,
|
|
179
|
+
seed,
|
|
180
|
+
coinType: COIN_TYPE,
|
|
181
|
+
account: ACCOUNT,
|
|
182
|
+
anchor,
|
|
183
|
+
memo: 'shielded-memo-padded-to-32-bytes'
|
|
184
|
+
});
|
|
185
|
+
expect(stateTransition).toEqual(expect.any(StateTransitionWASM));
|
|
186
|
+
}, 60000);
|
|
187
|
+
test('should be able to create a shieldedWithdrawal transition', () => {
|
|
188
|
+
const seed = randomBytes(32);
|
|
189
|
+
const { spend, anchor, owner } = makeSpendableNote(seed, BigInt(10000000000));
|
|
190
|
+
const stateTransition = sdk.shielded.createStateTransition('shieldedWithdrawal', {
|
|
191
|
+
spends: [spend],
|
|
192
|
+
withdrawalAmount: BigInt(1000000),
|
|
193
|
+
outputScript: CoreScriptWASM.newP2PKH(randomBytes(20)),
|
|
194
|
+
coreFeePerByte: 1,
|
|
195
|
+
pooling: 'Standard',
|
|
196
|
+
changeAddress: owner,
|
|
197
|
+
seed,
|
|
198
|
+
coinType: COIN_TYPE,
|
|
199
|
+
account: ACCOUNT,
|
|
200
|
+
anchor,
|
|
201
|
+
memo: 'shielded-memo-padded-to-32-bytes'
|
|
202
|
+
});
|
|
203
|
+
expect(stateTransition).toEqual(expect.any(StateTransitionWASM));
|
|
204
|
+
}, 60000);
|
|
205
|
+
// The proving step succeeds, but `IdentityCreateFromShieldedPoolResultWASM.identityId`
|
|
206
|
+
// (read while assembling the transition) rejects the derived id for synthetic notes.
|
|
207
|
+
// A real run needs a funded note and signed keys, so this is covered against a live pool.
|
|
208
|
+
test('should be able to create an identityCreateFromShieldedPool transition', () => {
|
|
209
|
+
const seed = randomBytes(32);
|
|
210
|
+
const { spend, anchor, owner } = makeSpendableNote(seed, BigInt(20000000000));
|
|
211
|
+
const privateKey = PrivateKeyWASM.fromHex('a1286dd195e2b8e1f6bdc946c56a53e0c544750d6452ddc0f4c593ef311f21af', 'testnet');
|
|
212
|
+
const identityPublicKeyInCreation = {
|
|
213
|
+
id: 0,
|
|
214
|
+
purpose: Purpose.AUTHENTICATION,
|
|
215
|
+
securityLevel: SecurityLevel.MASTER,
|
|
216
|
+
keyType: KeyType.ECDSA_SECP256K1,
|
|
217
|
+
readOnly: false,
|
|
218
|
+
data: privateKey.getPublicKey().bytes()
|
|
219
|
+
};
|
|
220
|
+
const stateTransition = sdk.shielded.createStateTransition('identityCreateFromShieldedPool', {
|
|
221
|
+
publicKeys: [identityPublicKeyInCreation],
|
|
222
|
+
privateKeys: [privateKey],
|
|
223
|
+
denomination: BigInt(10000000000),
|
|
224
|
+
sendToAddressOnCreationFailure: platformAddress(),
|
|
225
|
+
spends: [spend],
|
|
226
|
+
changeAddress: owner,
|
|
227
|
+
seed,
|
|
228
|
+
coinType: COIN_TYPE,
|
|
229
|
+
account: ACCOUNT,
|
|
230
|
+
anchor,
|
|
231
|
+
memo: 'shielded-memo-padded-to-32-bytes'
|
|
232
|
+
});
|
|
233
|
+
expect(stateTransition).toEqual(expect.any(StateTransitionWASM));
|
|
234
|
+
}, 60000);
|
|
235
|
+
});
|
|
236
|
+
});
|
package/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoreScriptWASM, DocumentWASM, GasFeesPaidByWASM, IdentifierLike, IdentifierWASM, KeyType, PlatformAddressWASM, Purpose, SecurityLevel, TokenEmergencyActionWASM, TokenPricingScheduleWASM } from 'pshenmic-dpp';
|
|
1
|
+
import { AddressFundsFeeStrategyStepWASM, AssetLockProofWASM, CoreScriptWASM, DocumentWASM, GasFeesPaidByWASM, IdentifierLike, IdentifierWASM, InputAddressWASM, KeyType, OrchardAddressWASM, PlatformAddressLike, PlatformAddressWASM, PlatformVersionWASM, PoolingLike, PrivateKeyWASM, Purpose, SecurityLevel, SpendableNoteWASM, TokenEmergencyActionWASM, TokenPricingScheduleWASM } from 'pshenmic-dpp';
|
|
2
2
|
export { CoreScriptWASM, DocumentWASM, GasFeesPaidByWASM, IdentifierWASM, KeyType, Purpose, SecurityLevel, TokenEmergencyActionWASM, TokenPricingScheduleWASM, StateTransitionWASM, BatchTransitionWASM, IdentityPublicKeyWASM, PrivateKeyWASM, DataContractUpdateTransitionWASM, IdentityWASM, IdentityUpdateTransitionWASM, IdentityCreditTransferWASM, MasternodeVoteTransitionWASM, IdentifierLike, PlatformAddressLike } from 'pshenmic-dpp';
|
|
3
3
|
export type Network = 'mainnet' | 'testnet';
|
|
4
4
|
export { DashPlatformSDK } from './src/DashPlatformSDK.js';
|
|
@@ -20,6 +20,80 @@ export interface DocumentTransitionParams {
|
|
|
20
20
|
gasFeesPaidBy: GasFeesPaidByWASM;
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
export interface ShieldedTransitionBaseParams {
|
|
24
|
+
/** version of the platform (defaults to the latest supported version) */
|
|
25
|
+
platformVersion?: PlatformVersionWASM;
|
|
26
|
+
}
|
|
27
|
+
/** Spend inputs shared by `shieldedWithdrawal`, `unshield`, `shieldedTransfer` and `identityCreateFromShieldedPool`. */
|
|
28
|
+
export interface ShieldedSpendParams extends ShieldedTransitionBaseParams {
|
|
29
|
+
spends: SpendableNoteWASM[];
|
|
30
|
+
changeAddress: OrchardAddressWASM;
|
|
31
|
+
seed: Uint8Array;
|
|
32
|
+
coinType: number;
|
|
33
|
+
account: number;
|
|
34
|
+
anchor: Uint8Array;
|
|
35
|
+
/** optional UTF-8 memo string (defaults to an empty memo) */
|
|
36
|
+
memo?: string;
|
|
37
|
+
}
|
|
38
|
+
/** Transparent platform addresses -> pool (deposit). */
|
|
39
|
+
export interface ShieldParams extends ShieldedTransitionBaseParams {
|
|
40
|
+
recipient: OrchardAddressWASM;
|
|
41
|
+
shieldAmount: bigint;
|
|
42
|
+
inputs: InputAddressWASM[];
|
|
43
|
+
privateKeys: PrivateKeyWASM[];
|
|
44
|
+
feeStrategy: AddressFundsFeeStrategyStepWASM[];
|
|
45
|
+
userFeeIncrease: number;
|
|
46
|
+
/** optional UTF-8 memo string (defaults to an empty memo) */
|
|
47
|
+
memo?: string;
|
|
48
|
+
senderOvk?: Uint8Array;
|
|
49
|
+
}
|
|
50
|
+
/** Asset lock -> pool (deposit). */
|
|
51
|
+
export interface ShieldFromAssetLockParams extends ShieldedTransitionBaseParams {
|
|
52
|
+
recipient: OrchardAddressWASM;
|
|
53
|
+
shieldAmount: bigint;
|
|
54
|
+
assetLockProof: AssetLockProofWASM;
|
|
55
|
+
privateKey: PrivateKeyWASM;
|
|
56
|
+
/** optional UTF-8 memo string (defaults to an empty memo) */
|
|
57
|
+
memo?: string;
|
|
58
|
+
dummyOutputs: number;
|
|
59
|
+
senderOvk?: Uint8Array;
|
|
60
|
+
surplusOutput?: PlatformAddressLike;
|
|
61
|
+
}
|
|
62
|
+
/** Pool -> core L1 (spend). */
|
|
63
|
+
export interface ShieldedWithdrawalParams extends ShieldedSpendParams {
|
|
64
|
+
withdrawalAmount: bigint;
|
|
65
|
+
outputScript: CoreScriptWASM;
|
|
66
|
+
coreFeePerByte: number;
|
|
67
|
+
pooling: PoolingLike;
|
|
68
|
+
}
|
|
69
|
+
/** Pool -> platform identity balance (spend). */
|
|
70
|
+
export interface UnshieldParams extends ShieldedSpendParams {
|
|
71
|
+
outputAddress: PlatformAddressLike;
|
|
72
|
+
unshieldAmount: bigint;
|
|
73
|
+
}
|
|
74
|
+
/** Pool -> pool (spend). */
|
|
75
|
+
export interface ShieldedTransferParams extends ShieldedSpendParams {
|
|
76
|
+
recipient: OrchardAddressWASM;
|
|
77
|
+
transferAmount: bigint;
|
|
78
|
+
}
|
|
79
|
+
/** Pool -> new identity (spend). */
|
|
80
|
+
export interface IdentityCreateFromShieldedPoolParams extends ShieldedSpendParams {
|
|
81
|
+
publicKeys: IdentityPublicKeyInCreation[];
|
|
82
|
+
privateKeys: PrivateKeyWASM[];
|
|
83
|
+
denomination: bigint;
|
|
84
|
+
sendToAddressOnCreationFailure: PlatformAddressLike;
|
|
85
|
+
}
|
|
86
|
+
/** Maps each shielded transition type to the params it requires. */
|
|
87
|
+
export interface ShieldedTransitionParamsMap {
|
|
88
|
+
shield: ShieldParams;
|
|
89
|
+
shieldFromAssetLock: ShieldFromAssetLockParams;
|
|
90
|
+
shieldedWithdrawal: ShieldedWithdrawalParams;
|
|
91
|
+
unshield: UnshieldParams;
|
|
92
|
+
shieldedTransfer: ShieldedTransferParams;
|
|
93
|
+
identityCreateFromShieldedPool: IdentityCreateFromShieldedPoolParams;
|
|
94
|
+
}
|
|
95
|
+
export type ShieldedTransitionType = keyof ShieldedTransitionParamsMap;
|
|
96
|
+
export type ShieldedTransitionParams = ShieldedTransitionParamsMap[ShieldedTransitionType];
|
|
23
97
|
export interface MasternodeInfo {
|
|
24
98
|
proTxHash: string;
|
|
25
99
|
address: string;
|
|
@@ -210,3 +284,13 @@ export interface PlatformAddressInfo {
|
|
|
210
284
|
nonce: number;
|
|
211
285
|
balance: bigint;
|
|
212
286
|
}
|
|
287
|
+
export interface ShieldedEncryptedNote {
|
|
288
|
+
nullifier: Uint8Array;
|
|
289
|
+
cmx: Uint8Array;
|
|
290
|
+
encryptedNote: Uint8Array;
|
|
291
|
+
cvNet: Uint8Array;
|
|
292
|
+
}
|
|
293
|
+
export interface ShieldedNullifierStatus {
|
|
294
|
+
nullifier: Uint8Array;
|
|
295
|
+
isSpent: boolean;
|
|
296
|
+
}
|