From 38f8b504f59c8bac2295337104ee0cf35ebb0408 Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 2 Sep 2025 16:59:58 -0400 Subject: [PATCH 01/13] fix(wasm-sdk): use SimpleSigner for unique identity key signatures - Replace SingleKeySigner with SimpleSigner for identity creation - Enable individual signing for each public key in identity - Add support for privateKeyHex/privateKeyWif for each key - Add comprehensive test coverage for all key type scenarios - Fix duplicate signature issue in identity state transitions This ensures each ECDSA_SECP256K1 and BLS12_381 key gets its own unique signature during identity creation, preventing signature verification errors. --- packages/wasm-sdk/Cargo.toml | 2 +- .../src/state_transitions/identity/mod.rs | 54 ++--- .../wasm-sdk/test/state-transitions.test.mjs | 192 ++++++++++++++++-- 3 files changed, 198 insertions(+), 50 deletions(-) diff --git a/packages/wasm-sdk/Cargo.toml b/packages/wasm-sdk/Cargo.toml index ced15c058e2..9478867423d 100644 --- a/packages/wasm-sdk/Cargo.toml +++ b/packages/wasm-sdk/Cargo.toml @@ -25,7 +25,7 @@ token_reward_explanations = ["dash-sdk/token_reward_explanations"] [dependencies] dash-sdk = { path = "../rs-sdk", default-features = false } -simple-signer = { path = "../simple-signer" } +simple-signer = { path = "../simple-signer", features = ["state-transitions"] } drive = { path = "../rs-drive", default-features = false, features = ["verify"] } console_error_panic_hook = { version = "0.1.6" } thiserror = { version = "2.0.12" } diff --git a/packages/wasm-sdk/src/state_transitions/identity/mod.rs b/packages/wasm-sdk/src/state_transitions/identity/mod.rs index 995d43e94be..0f1bcfc3ae0 100644 --- a/packages/wasm-sdk/src/state_transitions/identity/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/identity/mod.rs @@ -9,7 +9,7 @@ use dash_sdk::dpp::state_transition::identity_credit_transfer_transition::Identi use dash_sdk::dpp::state_transition::identity_credit_transfer_transition::methods::IdentityCreditTransferTransitionMethodsV0; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::Fetch; -use simple_signer::SingleKeySigner; +use simple_signer::{SingleKeySigner, signer::SimpleSigner}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; use js_sys; @@ -80,8 +80,9 @@ impl WasmSdk { let keys_array = keys_data.as_array() .ok_or_else(|| JsValue::from_str("public_keys must be a JSON array"))?; - // Create identity public keys + // Create identity public keys and collect private keys for signing let mut identity_public_keys = std::collections::BTreeMap::new(); + let mut signer = SimpleSigner::default(); let mut key_id = 0u32; for key_data in keys_array { @@ -122,33 +123,34 @@ impl WasmSdk { _ => SecurityLevel::HIGH }; - // Check if privateKeyHex is provided - if so, derive public key data from it - let public_key_data = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { + // Get private key - required for signing each key + let private_key_bytes = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { // Decode private key from hex - let private_key_bytes = hex::decode(private_key_hex) + let bytes = hex::decode(private_key_hex) .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; - if private_key_bytes.len() != 32 { - return Err(JsValue::from_str(&format!("Private key must be 32 bytes, got {}", private_key_bytes.len()))); + if bytes.len() != 32 { + return Err(JsValue::from_str(&format!("Private key must be 32 bytes, got {}", bytes.len()))); } let mut private_key_array = [0u8; 32]; - private_key_array.copy_from_slice(&private_key_bytes); - - // Use DPP's built-in method to get the correct public key data for the key type - // Use network from SDK configuration - key_type.public_key_data_from_private_key_data( - &private_key_array, - self.network() - ).map_err(|e| JsValue::from_str(&format!("Failed to derive public key data: {}", e)))? - } else if let Some(data_str) = key_data["data"].as_str() { - // Fall back to using provided data (base64 encoded) - dash_sdk::dpp::dashcore::base64::decode(data_str) - .map_err(|e| JsValue::from_str(&format!("Invalid base64 key data: {}", e)))? + private_key_array.copy_from_slice(&bytes); + private_key_array + } else if let Some(private_key_wif) = key_data["privateKeyWif"].as_str() { + // Parse WIF format private key + let private_key = PrivateKey::from_wif(private_key_wif) + .map_err(|e| JsValue::from_str(&format!("Invalid WIF private key: {}", e)))?; + private_key.inner.secret_bytes() } else { - return Err(JsValue::from_str("Either privateKeyHex or data must be provided")); + return Err(JsValue::from_str("Either privateKeyHex or privateKeyWif must be provided for each key")); }; + // Derive public key data from private key + let public_key_data = key_type.public_key_data_from_private_key_data( + &private_key_bytes, + self.network() + ).map_err(|e| JsValue::from_str(&format!("Failed to derive public key data: {}", e)))?; + // Create the identity public key use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; let public_key = IdentityPublicKey::V0(IdentityPublicKeyV0 { @@ -162,6 +164,9 @@ impl WasmSdk { disabled_at: None, }); + // Add the public key and its private key to the signer + signer.add_key(public_key.clone(), private_key_bytes); + identity_public_keys.insert(key_id, public_key); key_id += 1; } @@ -175,13 +180,8 @@ impl WasmSdk { revision: 0, }); - // Create signer from asset lock proof private key using SDK's network configuration - let signer = SingleKeySigner::from_string(&asset_lock_proof_private_key, self.network()) - .map_err(|e| { - let error_msg = format!("Invalid private key: {}", e); - web_sys::console::error_1(&JsValue::from_str(&error_msg)); - JsValue::from_str(&error_msg) - })?; + // Use the SimpleSigner we built with all the identity keys + // The signer now contains all private keys for signing each public key individually // Put identity to platform and wait let created_identity = match identity diff --git a/packages/wasm-sdk/test/state-transitions.test.mjs b/packages/wasm-sdk/test/state-transitions.test.mjs index 9f8fb34c371..64695c9acec 100644 --- a/packages/wasm-sdk/test/state-transitions.test.mjs +++ b/packages/wasm-sdk/test/state-transitions.test.mjs @@ -20,7 +20,8 @@ if (!global.crypto) { } // Import WASM SDK -import init, * as wasmSdk from '../pkg/wasm_sdk.js'; +import init, { generate_key_pair } from '../pkg/wasm_sdk.js'; +import * as wasmSdk from '../pkg/wasm_sdk.js'; // Initialize WASM console.log('Initializing WASM SDK...'); @@ -63,21 +64,168 @@ const TOKEN_CONTRACT = 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv'; // Identity State Transitions describe('Identity State Transitions'); -await test('identity_create - requires funding', async () => { +await test('identity_create with all SECP256K1 keys (common scenario)', async () => { try { - // Would need funding transaction - const result = await wasmSdk.identity_create( - sdk, - TEST_MNEMONIC, - null, // no alias - 0 // key index + // Generate unique keys for testing (1 asset lock + 3 identity keys) + const assetLockKey = generate_key_pair("testnet"); + const secp256k1Key1 = generate_key_pair("testnet"); + const secp256k1Key2 = generate_key_pair("testnet"); + const secp256k1Key3 = generate_key_pair("testnet"); + + // Mock asset lock proof + const mockAssetLockProof = JSON.stringify({ + coreChainLockedHeight: 1000000, + outPoint: "0000000000000000000000000000000000000000000000000000000000000000:0" + }); + + // Create public keys array with all SECP256K1 keys + const publicKeys = JSON.stringify([ + { + keyType: "ECDSA_SECP256K1", + purpose: "AUTHENTICATION", + securityLevel: "MASTER", + privateKeyHex: secp256k1Key1.private_key_hex + }, + { + keyType: "ECDSA_SECP256K1", + purpose: "AUTHENTICATION", + securityLevel: "CRITICAL", + privateKeyHex: secp256k1Key2.private_key_hex + }, + { + keyType: "ECDSA_SECP256K1", + purpose: "ENCRYPTION", + securityLevel: "HIGH", + privateKeyHex: secp256k1Key3.private_key_hex + } + ]); + + const result = await sdk.identityCreate( + mockAssetLockProof, + assetLockKey.private_key_wif, + publicKeys ); - throw new Error('Should fail without funding'); + throw new Error('Should fail with mock data'); } catch (error) { - if (error.message.includes('Should fail')) { + const errorMessage = error.message || error.toString() || 'Unknown error'; + if (errorMessage.includes('Should fail')) { throw error; } - console.log(' Expected error without funding'); + // Check that it's NOT a signature verification error + if (errorMessage.includes('signature failed verification')) { + throw new Error('SIGNATURE VERIFICATION ERROR - SimpleSigner may not be working correctly'); + } + console.log(' Expected error with mock data (not signature error)'); + } +}); + +await test('identity_create with mixed key types (SECP256K1 and HASH160)', async () => { + try { + // Generate unique keys for testing (1 asset lock + 2 SECP256K1 + 1 HASH160) + const assetLockKey = generate_key_pair("testnet"); + const secp256k1Key1 = generate_key_pair("testnet"); + const secp256k1Key2 = generate_key_pair("testnet"); + const hash160Key = generate_key_pair("testnet"); + + // Mock asset lock proof + const mockAssetLockProof = JSON.stringify({ + coreChainLockedHeight: 1000000, + outPoint: "0000000000000000000000000000000000000000000000000000000000000001:0" + }); + + // Create mixed public keys array + const publicKeys = JSON.stringify([ + { + keyType: "ECDSA_SECP256K1", + purpose: "AUTHENTICATION", + securityLevel: "MASTER", + privateKeyHex: secp256k1Key1.private_key_hex + }, + { + keyType: "ECDSA_SECP256K1", + purpose: "AUTHENTICATION", + securityLevel: "CRITICAL", + privateKeyHex: secp256k1Key2.private_key_hex + }, + { + keyType: "ECDSA_HASH160", + purpose: "TRANSFER", + securityLevel: "HIGH", + privateKeyHex: hash160Key.private_key_hex + } + ]); + + const result = await sdk.identityCreate( + mockAssetLockProof, + assetLockKey.private_key_wif, + publicKeys + ); + throw new Error('Should fail with mock data'); + } catch (error) { + const errorMessage = error.message || error.toString() || 'Unknown error'; + if (errorMessage.includes('Should fail')) { + throw error; + } + // Check that it's NOT a signature verification error + if (errorMessage.includes('signature failed verification')) { + throw new Error('SIGNATURE VERIFICATION ERROR - SimpleSigner may not be working correctly'); + } + console.log(' Expected error with mock data (not signature error)'); + } +}); + +await test('identity_create with only HASH160 keys', async () => { + try { + // Generate unique keys for testing (1 asset lock + 3 HASH160) + const assetLockKey = generate_key_pair("testnet"); + const hash160Key1 = generate_key_pair("testnet"); + const hash160Key2 = generate_key_pair("testnet"); + const hash160Key3 = generate_key_pair("testnet"); + + // Mock asset lock proof + const mockAssetLockProof = JSON.stringify({ + coreChainLockedHeight: 1000000, + outPoint: "0000000000000000000000000000000000000000000000000000000000000002:0" + }); + + // Create public keys array with all HASH160 keys + const publicKeys = JSON.stringify([ + { + keyType: "ECDSA_HASH160", + purpose: "AUTHENTICATION", + securityLevel: "MASTER", + privateKeyHex: hash160Key1.private_key_hex + }, + { + keyType: "ECDSA_HASH160", + purpose: "AUTHENTICATION", + securityLevel: "CRITICAL", + privateKeyHex: hash160Key2.private_key_hex + }, + { + keyType: "ECDSA_HASH160", + purpose: "TRANSFER", + securityLevel: "HIGH", + privateKeyHex: hash160Key3.private_key_hex + } + ]); + + const result = await sdk.identityCreate( + mockAssetLockProof, + assetLockKey.private_key_wif, + publicKeys + ); + throw new Error('Should fail with mock data'); + } catch (error) { + const errorMessage = error.message || error.toString() || 'Unknown error'; + if (errorMessage.includes('Should fail')) { + throw error; + } + // Check that it's NOT a signature verification error + if (errorMessage.includes('signature failed verification')) { + throw new Error('SIGNATURE VERIFICATION ERROR - SimpleSigner may not be working correctly'); + } + console.log(' Expected error with mock data (not signature error)'); } }); @@ -144,17 +292,17 @@ await test('identity_withdraw - requires balance', async () => { } }); -await test('identity_put - causes panic in WASM', async () => { - try { - const result = await wasmSdk.identity_put(sdk); - throw new Error('Should have panicked'); - } catch (error) { - if (error.message.includes('Should have panicked')) { - throw error; - } - console.log(' Expected panic (known issue)'); - } -}); +// await test('identity_put - causes panic in WASM', async () => { +// try { +// const result = await wasmSdk.identity_put(sdk); +// throw new Error('Should have panicked'); +// } catch (error) { +// if (error.message.includes('Should have panicked')) { +// throw error; +// } +// console.log(' Expected panic (known issue)'); +// } +// }); // Document State Transitions describe('Document State Transitions'); From 4bfbec712442eaa770b59eafcf5fb5ea98d95f0e Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 2 Sep 2025 17:03:01 -0400 Subject: [PATCH 02/13] test(wasm-sdk): re-add dropped test --- .../wasm-sdk/test/state-transitions.test.mjs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/wasm-sdk/test/state-transitions.test.mjs b/packages/wasm-sdk/test/state-transitions.test.mjs index 64695c9acec..72a113f911c 100644 --- a/packages/wasm-sdk/test/state-transitions.test.mjs +++ b/packages/wasm-sdk/test/state-transitions.test.mjs @@ -64,6 +64,24 @@ const TOKEN_CONTRACT = 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv'; // Identity State Transitions describe('Identity State Transitions'); +await test('identity_create - requires funding', async () => { + try { + // Would need funding transaction + const result = await wasmSdk.identity_create( + sdk, + TEST_MNEMONIC, + null, // no alias + 0 // key index + ); + throw new Error('Should fail without funding'); + } catch (error) { + if (error.message.includes('Should fail')) { + throw error; + } + console.log(' Expected error without funding'); + } +}); + await test('identity_create with all SECP256K1 keys (common scenario)', async () => { try { // Generate unique keys for testing (1 asset lock + 3 identity keys) From 94d767af38edce41ac31e5271a7e2a7ce2b77054 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 09:57:31 -0400 Subject: [PATCH 03/13] feat(wasm-sdk): add key type selection for identity creation testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add keyType selector to identity creation interface with HASH160/SECP256K1 options - Update api-definitions.json with new keyType input field and clearer descriptions - Modify index.html to dynamically use selected key type for all generated keys - Add real-time preview updates showing signature requirements per key type - Enable testing of both traditional HASH160 behavior and SECP256K1 SimpleSigner fix 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/wasm-sdk/api-definitions.json | 18 ++++++++++++++++++ packages/wasm-sdk/index.html | 22 ++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/wasm-sdk/api-definitions.json b/packages/wasm-sdk/api-definitions.json index 4e959b3c527..feaec6d7221 100644 --- a/packages/wasm-sdk/api-definitions.json +++ b/packages/wasm-sdk/api-definitions.json @@ -1234,6 +1234,24 @@ ], "help": "Default adds standard keys for most Platform operations. Advanced allows custom key configuration." }, + { + "name": "keyType", + "type": "select", + "label": "Key Type", + "required": true, + "value": "ECDSA_HASH160", + "options": [ + { + "value": "ECDSA_HASH160", + "label": "ECDSA_HASH160 (Dash Evo Tool default)" + }, + { + "value": "ECDSA_SECP256K1", + "label": "ECDSA_SECP256K1 (Dash mobile wallet default)" + } + ], + "help": "Choose key type" + }, { "name": "keyPreview", "type": "keyPreview", diff --git a/packages/wasm-sdk/index.html b/packages/wasm-sdk/index.html index a830f4dbadf..fac5ac9c4f2 100644 --- a/packages/wasm-sdk/index.html +++ b/packages/wasm-sdk/index.html @@ -3062,6 +3062,7 @@

Results

const seedPhrase = values.seedPhrase?.trim(); const identityIndex = parseInt(values.identityIndex || '0'); const keySelectionMode = values.keySelectionMode || 'default'; + const selectedKeyType = values.keyType || 'ECDSA_HASH160'; if (!seedPhrase || !validate_mnemonic(seedPhrase)) { throw new Error('Invalid seed phrase'); @@ -3089,10 +3090,11 @@

Results

// Create public key objects for the SDK // Pass private keys so the SDK can derive the correct public key data using DPP + // Use the selected key type for all keys publicKeys = [ { id: 0, - keyType: "ECDSA_HASH160", + keyType: selectedKeyType, purpose: "AUTHENTICATION", securityLevel: "MASTER", privateKeyHex: masterKey.private_key_hex, @@ -3100,7 +3102,7 @@

Results

}, { id: 1, - keyType: "ECDSA_HASH160", + keyType: selectedKeyType, purpose: "AUTHENTICATION", securityLevel: "HIGH", privateKeyHex: authKey.private_key_hex, @@ -3108,7 +3110,7 @@

Results

}, { id: 2, - keyType: "ECDSA_HASH160", + keyType: selectedKeyType, purpose: "TRANSFER", securityLevel: "CRITICAL", privateKeyHex: transferKey.private_key_hex, @@ -4298,6 +4300,7 @@

Results

const seedPhrase = dynamicInputs.querySelector('[name="seedPhrase"]')?.value?.trim(); const identityIndex = parseInt(dynamicInputs.querySelector('[name="identityIndex"]')?.value || '0'); const keySelectionMode = dynamicInputs.querySelector('[name="keySelectionMode"]')?.value || 'default'; + const selectedKeyType = dynamicInputs.querySelector('[name="keyType"]')?.value || 'ECDSA_HASH160'; const previewContainer = document.getElementById('keyPreviewContainer'); if (!seedPhrase || !previewContainer) return; @@ -4325,21 +4328,26 @@

Results

const transferKeyPath = `m/9'/${currentNetwork === 'mainnet' ? 5 : 1}'/5'/0'/0'/${identityIndex}'/2'`; const transferKey = derive_key_from_seed_with_path(seedPhrase, undefined, transferKeyPath, currentNetwork); + // Show signature requirement info + const signatureInfo = selectedKeyType === 'ECDSA_SECP256K1' + ? ' (Individual signatures required)' + : ' (No individual signatures required)'; + keysHtml += `
Master Key:
${masterKey.private_key_wif}
- Purpose: Authentication | Security: Master | Type: ECDSA_HASH160 + Purpose: Authentication | Security: Master | Type: ${selectedKeyType}${signatureInfo}
Key 1:
${authKey.private_key_wif}
- Purpose: Authentication | Security: High | Type: ECDSA_HASH160 + Purpose: Authentication | Security: High | Type: ${selectedKeyType}${signatureInfo}
Key 2:
${transferKey.private_key_wif}
- Purpose: Transfer | Security: Critical | Type: ECDSA_HASH160 + Purpose: Transfer | Security: Critical | Type: ${selectedKeyType}${signatureInfo}
`; @@ -4358,10 +4366,12 @@

Results

const seedInput = dynamicInputs.querySelector('[name="seedPhrase"]'); const indexInput = dynamicInputs.querySelector('[name="identityIndex"]'); const modeSelect = dynamicInputs.querySelector('[name="keySelectionMode"]'); + const keyTypeSelect = dynamicInputs.querySelector('[name="keyType"]'); if (seedInput) seedInput.addEventListener('input', updateKeyPreview); if (indexInput) indexInput.addEventListener('input', updateKeyPreview); if (modeSelect) modeSelect.addEventListener('change', updateKeyPreview); + if (keyTypeSelect) keyTypeSelect.addEventListener('change', updateKeyPreview); // Initial update updateKeyPreview(); From 1dd29784609acc659bd5bd49dad5fe8797c29ed0 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 10:03:09 -0400 Subject: [PATCH 04/13] docs(wasm-sdk): regenerate docs with updated identity create info --- packages/wasm-sdk/AI_REFERENCE.md | 10 ++++++---- packages/wasm-sdk/api-definitions.json | 4 ++-- packages/wasm-sdk/docs.html | 10 ++++++---- packages/wasm-sdk/docs_manifest.json | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/wasm-sdk/AI_REFERENCE.md b/packages/wasm-sdk/AI_REFERENCE.md index dfd9f2eb948..5e357be14d8 100644 --- a/packages/wasm-sdk/AI_REFERENCE.md +++ b/packages/wasm-sdk/AI_REFERENCE.md @@ -763,7 +763,7 @@ Parameters: - `assetLockProofPrivateKey` (string, required) - Asset Lock Proof Private Key - WIF format private key - `publicKeys` (string, required) - Public Keys - - JSON array of public keys + - JSON array of public keys with private keys for signing. ECDSA_SECP256K1 and BLS12_381 keys require privateKeyHex field. Example: ```javascript @@ -771,7 +771,7 @@ Example: const assetLockProof = "a9147d3b... (hex-encoded)"; const assetLockProofPrivateKey = "XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1"; // WIF format -// Public keys array with proper key types +// Public keys array with proper key types and private keys for signing const publicKeys = JSON.stringify([ { id: 0, @@ -779,15 +779,17 @@ const publicKeys = JSON.stringify([ purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc. securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3 data: "A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ", // Base64-encoded public key - readOnly: false + readOnly: false, + privateKeyHex: "d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7" // Private key for this public key }, { id: 1, - type: 0, + type: 2, // ECDSA_HASH160 - doesn't require private key for signing purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key readOnly: false + // Note: ECDSA_HASH160 keys don't need privateKeyHex as they don't get individual signatures } ]); diff --git a/packages/wasm-sdk/api-definitions.json b/packages/wasm-sdk/api-definitions.json index feaec6d7221..ed99722f290 100644 --- a/packages/wasm-sdk/api-definitions.json +++ b/packages/wasm-sdk/api-definitions.json @@ -1279,10 +1279,10 @@ "type": "string", "label": "Public Keys", "required": true, - "description": "JSON array of public keys" + "description": "JSON array of public keys with private keys for signing. ECDSA_SECP256K1 and BLS12_381 keys require privateKeyHex field." } ], - "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false\n },\n {\n id: 1,\n type: 0,\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" + "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n type: 2, // ECDSA_HASH160 - doesn't require private key for signing\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false\n // Note: ECDSA_HASH160 keys don't need privateKeyHex as they don't get individual signatures\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" }, "identityTopUp": { "label": "Identity Top Up", diff --git a/packages/wasm-sdk/docs.html b/packages/wasm-sdk/docs.html index 167b857b71f..e447daa0011 100644 --- a/packages/wasm-sdk/docs.html +++ b/packages/wasm-sdk/docs.html @@ -1967,7 +1967,7 @@
Parameters:
Public Keys string (required) -
JSON array of public keys +
JSON array of public keys with private keys for signing. ECDSA_SECP256K1 and BLS12_381 keys require privateKeyHex field. @@ -1977,7 +1977,7 @@
Example
const assetLockProof = "a9147d3b... (hex-encoded)"; const assetLockProofPrivateKey = "XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1"; // WIF format -// Public keys array with proper key types +// Public keys array with proper key types and private keys for signing const publicKeys = JSON.stringify([ { id: 0, @@ -1985,15 +1985,17 @@
Example
purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc. securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3 data: "A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ", // Base64-encoded public key - readOnly: false + readOnly: false, + privateKeyHex: "d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7" // Private key for this public key }, { id: 1, - type: 0, + type: 2, // ECDSA_HASH160 - doesn't require private key for signing purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key readOnly: false + // Note: ECDSA_HASH160 keys don't need privateKeyHex as they don't get individual signatures } ]); diff --git a/packages/wasm-sdk/docs_manifest.json b/packages/wasm-sdk/docs_manifest.json index 252a50417e2..719e305a54e 100644 --- a/packages/wasm-sdk/docs_manifest.json +++ b/packages/wasm-sdk/docs_manifest.json @@ -1,5 +1,5 @@ { - "generated_at": "2025-08-18T19:21:21.062910+00:00", + "generated_at": "2025-09-03T13:57:39.221904+00:00", "queries": { "getIdentity": { "category": "identity", From 315d1bfec4b53b21b9024ba0caede1569534f6f2 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 10:04:19 -0400 Subject: [PATCH 05/13] chore(wasm-sdk): update identity create comment based on changes --- .../wasm-sdk/src/state_transitions/identity/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/wasm-sdk/src/state_transitions/identity/mod.rs b/packages/wasm-sdk/src/state_transitions/identity/mod.rs index 0f1bcfc3ae0..d729b0c3148 100644 --- a/packages/wasm-sdk/src/state_transitions/identity/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/identity/mod.rs @@ -27,7 +27,15 @@ impl WasmSdk { /// /// * `asset_lock_proof` - The asset lock proof (transaction hex) /// * `asset_lock_proof_private_key` - The private key that controls the asset lock - /// * `public_keys` - JSON array of public keys to add to the identity + /// * `public_keys` - JSON array of public keys to add to the identity. Each key object must include: + /// - For ECDSA_SECP256K1 and BLS12_381 keys: `privateKeyHex` field is required for individual signing + /// - For ECDSA_HASH160 keys: `privateKeyHex` is not required (empty signatures are used) + /// + /// # Implementation Notes + /// + /// This function uses SimpleSigner to provide individual signatures for each public key as required. + /// Each ECDSA_SECP256K1 and BLS12_381 key will be signed with its corresponding private key, + /// ensuring unique signatures per key as required by DPP validation. /// /// # Returns /// From cda7e9c89b0535f30ed42c66b98076855a81ca92 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 11:31:11 -0400 Subject: [PATCH 06/13] chore: address review comments Update code comments / docs for consistency --- packages/wasm-sdk/AI_REFERENCE.md | 10 +++++----- packages/wasm-sdk/api-definitions.json | 4 ++-- packages/wasm-sdk/docs.html | 10 +++++----- packages/wasm-sdk/docs_manifest.json | 2 +- .../wasm-sdk/src/state_transitions/identity/mod.rs | 5 +++-- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/wasm-sdk/AI_REFERENCE.md b/packages/wasm-sdk/AI_REFERENCE.md index 5e357be14d8..3cbf0cb38b3 100644 --- a/packages/wasm-sdk/AI_REFERENCE.md +++ b/packages/wasm-sdk/AI_REFERENCE.md @@ -763,7 +763,7 @@ Parameters: - `assetLockProofPrivateKey` (string, required) - Asset Lock Proof Private Key - WIF format private key - `publicKeys` (string, required) - Public Keys - - JSON array of public keys with private keys for signing. ECDSA_SECP256K1 and BLS12_381 keys require privateKeyHex field. + - JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWIF, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash). Example: ```javascript @@ -771,7 +771,7 @@ Example: const assetLockProof = "a9147d3b... (hex-encoded)"; const assetLockProofPrivateKey = "XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1"; // WIF format -// Public keys array with proper key types and private keys for signing +// Public keys array with proper key types and private keys for signing/hashing const publicKeys = JSON.stringify([ { id: 0, @@ -784,12 +784,12 @@ const publicKeys = JSON.stringify([ }, { id: 1, - type: 2, // ECDSA_HASH160 - doesn't require private key for signing + type: 2, // ECDSA_HASH160 - requires private key to derive public key hash purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key - readOnly: false - // Note: ECDSA_HASH160 keys don't need privateKeyHex as they don't get individual signatures + readOnly: false, + privateKeyHex: "e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3" // For deriving public key hash } ]); diff --git a/packages/wasm-sdk/api-definitions.json b/packages/wasm-sdk/api-definitions.json index ed99722f290..e3ccbd07efc 100644 --- a/packages/wasm-sdk/api-definitions.json +++ b/packages/wasm-sdk/api-definitions.json @@ -1279,10 +1279,10 @@ "type": "string", "label": "Public Keys", "required": true, - "description": "JSON array of public keys with private keys for signing. ECDSA_SECP256K1 and BLS12_381 keys require privateKeyHex field." + "description": "JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWIF, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash)." } ], - "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n type: 2, // ECDSA_HASH160 - doesn't require private key for signing\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false\n // Note: ECDSA_HASH160 keys don't need privateKeyHex as they don't get individual signatures\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" + "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n type: 2, // ECDSA_HASH160 - requires private key to derive public key hash\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3\" // For deriving public key hash\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" }, "identityTopUp": { "label": "Identity Top Up", diff --git a/packages/wasm-sdk/docs.html b/packages/wasm-sdk/docs.html index e447daa0011..52a0e8c9062 100644 --- a/packages/wasm-sdk/docs.html +++ b/packages/wasm-sdk/docs.html @@ -1967,7 +1967,7 @@
Parameters:
Public Keys string (required) -
JSON array of public keys with private keys for signing. ECDSA_SECP256K1 and BLS12_381 keys require privateKeyHex field. +
JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWIF, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash). @@ -1977,7 +1977,7 @@
Example
const assetLockProof = "a9147d3b... (hex-encoded)"; const assetLockProofPrivateKey = "XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1"; // WIF format -// Public keys array with proper key types and private keys for signing +// Public keys array with proper key types and private keys for signing/hashing const publicKeys = JSON.stringify([ { id: 0, @@ -1990,12 +1990,12 @@
Example
}, { id: 1, - type: 2, // ECDSA_HASH160 - doesn't require private key for signing + type: 2, // ECDSA_HASH160 - requires private key to derive public key hash purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key - readOnly: false - // Note: ECDSA_HASH160 keys don't need privateKeyHex as they don't get individual signatures + readOnly: false, + privateKeyHex: "e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3" // For deriving public key hash } ]); diff --git a/packages/wasm-sdk/docs_manifest.json b/packages/wasm-sdk/docs_manifest.json index 719e305a54e..364c7b0aa25 100644 --- a/packages/wasm-sdk/docs_manifest.json +++ b/packages/wasm-sdk/docs_manifest.json @@ -1,5 +1,5 @@ { - "generated_at": "2025-09-03T13:57:39.221904+00:00", + "generated_at": "2025-09-03T15:28:51.637796+00:00", "queries": { "getIdentity": { "category": "identity", diff --git a/packages/wasm-sdk/src/state_transitions/identity/mod.rs b/packages/wasm-sdk/src/state_transitions/identity/mod.rs index d729b0c3148..3a11c273e7f 100644 --- a/packages/wasm-sdk/src/state_transitions/identity/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/identity/mod.rs @@ -28,8 +28,9 @@ impl WasmSdk { /// * `asset_lock_proof` - The asset lock proof (transaction hex) /// * `asset_lock_proof_private_key` - The private key that controls the asset lock /// * `public_keys` - JSON array of public keys to add to the identity. Each key object must include: - /// - For ECDSA_SECP256K1 and BLS12_381 keys: `privateKeyHex` field is required for individual signing - /// - For ECDSA_HASH160 keys: `privateKeyHex` is not required (empty signatures are used) + /// - All key types require either `privateKeyHex` or `privateKeyWif` field + /// - ECDSA_SECP256K1 and BLS12_381 keys: Used for individual key signatures + /// - ECDSA_HASH160 keys: Used to derive the public key hash (produces empty signatures) /// /// # Implementation Notes /// From 12927417b3223091ea55922062aa5008ff0b4d5e Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 11:40:59 -0400 Subject: [PATCH 07/13] docs: fix example and regenerate docs Incorrect field name was used in an example --- packages/wasm-sdk/AI_REFERENCE.md | 4 ++-- packages/wasm-sdk/api-definitions.json | 2 +- packages/wasm-sdk/docs.html | 4 ++-- packages/wasm-sdk/docs_manifest.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/wasm-sdk/AI_REFERENCE.md b/packages/wasm-sdk/AI_REFERENCE.md index 3cbf0cb38b3..b72e40d13d6 100644 --- a/packages/wasm-sdk/AI_REFERENCE.md +++ b/packages/wasm-sdk/AI_REFERENCE.md @@ -775,7 +775,7 @@ const assetLockProofPrivateKey = "XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThe const publicKeys = JSON.stringify([ { id: 0, - type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2 + keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2 purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc. securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3 data: "A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ", // Base64-encoded public key @@ -784,7 +784,7 @@ const publicKeys = JSON.stringify([ }, { id: 1, - type: 2, // ECDSA_HASH160 - requires private key to derive public key hash + keyType: 2, // ECDSA_HASH160 - requires private key to derive public key hash purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key diff --git a/packages/wasm-sdk/api-definitions.json b/packages/wasm-sdk/api-definitions.json index e3ccbd07efc..61b69c5a464 100644 --- a/packages/wasm-sdk/api-definitions.json +++ b/packages/wasm-sdk/api-definitions.json @@ -1282,7 +1282,7 @@ "description": "JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWIF, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash)." } ], - "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n type: 2, // ECDSA_HASH160 - requires private key to derive public key hash\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3\" // For deriving public key hash\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" + "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n keyType: 2, // ECDSA_HASH160 - requires private key to derive public key hash\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3\" // For deriving public key hash\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" }, "identityTopUp": { "label": "Identity Top Up", diff --git a/packages/wasm-sdk/docs.html b/packages/wasm-sdk/docs.html index 52a0e8c9062..3f5f8f04f94 100644 --- a/packages/wasm-sdk/docs.html +++ b/packages/wasm-sdk/docs.html @@ -1981,7 +1981,7 @@
Example
const publicKeys = JSON.stringify([ { id: 0, - type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2 + keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2 purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc. securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3 data: "A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ", // Base64-encoded public key @@ -1990,7 +1990,7 @@
Example
}, { id: 1, - type: 2, // ECDSA_HASH160 - requires private key to derive public key hash + keyType: 2, // ECDSA_HASH160 - requires private key to derive public key hash purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key diff --git a/packages/wasm-sdk/docs_manifest.json b/packages/wasm-sdk/docs_manifest.json index 364c7b0aa25..d111d84319e 100644 --- a/packages/wasm-sdk/docs_manifest.json +++ b/packages/wasm-sdk/docs_manifest.json @@ -1,5 +1,5 @@ { - "generated_at": "2025-09-03T15:28:51.637796+00:00", + "generated_at": "2025-09-03T15:38:54.314693+00:00", "queries": { "getIdentity": { "category": "identity", From 2ce6bd23062d53f77d83739ac75ecbbb9dfd820c Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 12:09:41 -0400 Subject: [PATCH 08/13] chore: typo fix --- packages/wasm-sdk/api-definitions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wasm-sdk/api-definitions.json b/packages/wasm-sdk/api-definitions.json index 61b69c5a464..d815053ca74 100644 --- a/packages/wasm-sdk/api-definitions.json +++ b/packages/wasm-sdk/api-definitions.json @@ -1279,7 +1279,7 @@ "type": "string", "label": "Public Keys", "required": true, - "description": "JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWIF, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash)." + "description": "JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWif, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash)." } ], "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n keyType: 2, // ECDSA_HASH160 - requires private key to derive public key hash\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3\" // For deriving public key hash\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" From 6e9ca16e289ff408c6b1dc10000c16cbf555e153 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 13:20:40 -0400 Subject: [PATCH 09/13] fix(wasm-sdk): support both privateKeyHex and data fields for ECDSA_HASH160 keys Allow ECDSA_HASH160 keys to be provided either as: - privateKeyHex: derive the 20-byte hash from the private key - data: direct base64-encoded 20-byte public key hash This maintains backward compatibility while addressing the review feedback that ECDSA_HASH160 keys don't require private keys for cryptographic operations. --- packages/wasm-sdk/AI_REFERENCE.md | 6 +- packages/wasm-sdk/api-definitions.json | 4 +- packages/wasm-sdk/docs.html | 6 +- packages/wasm-sdk/docs_manifest.json | 2 +- .../src/state_transitions/identity/mod.rs | 121 +++++++++++++----- 5 files changed, 99 insertions(+), 40 deletions(-) diff --git a/packages/wasm-sdk/AI_REFERENCE.md b/packages/wasm-sdk/AI_REFERENCE.md index b72e40d13d6..12d4555f9dd 100644 --- a/packages/wasm-sdk/AI_REFERENCE.md +++ b/packages/wasm-sdk/AI_REFERENCE.md @@ -763,7 +763,7 @@ Parameters: - `assetLockProofPrivateKey` (string, required) - Asset Lock Proof Private Key - WIF format private key - `publicKeys` (string, required) - Public Keys - - JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWIF, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash). + - JSON array of public keys. Key requirements: ECDSA_SECP256K1 and BLS12_381 require privateKeyHex or privateKeyWif for signing, ECDSA_HASH160 requires only the data field (base64-encoded 20-byte public key hash). Example: ```javascript @@ -784,12 +784,12 @@ const publicKeys = JSON.stringify([ }, { id: 1, - keyType: 2, // ECDSA_HASH160 - requires private key to derive public key hash + keyType: 2, // ECDSA_HASH160 - only needs data field (public key hash) purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key readOnly: false, - privateKeyHex: "e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3" // For deriving public key hash + // No private key needed for ECDSA_HASH160 } ]); diff --git a/packages/wasm-sdk/api-definitions.json b/packages/wasm-sdk/api-definitions.json index d815053ca74..691f07a6980 100644 --- a/packages/wasm-sdk/api-definitions.json +++ b/packages/wasm-sdk/api-definitions.json @@ -1279,10 +1279,10 @@ "type": "string", "label": "Public Keys", "required": true, - "description": "JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWif, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash)." + "description": "JSON array of public keys. Key requirements: ECDSA_SECP256K1 and BLS12_381 require privateKeyHex or privateKeyWif for signing, ECDSA_HASH160 requires only the data field (base64-encoded 20-byte public key hash)." } ], - "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n keyType: 2, // ECDSA_HASH160 - requires private key to derive public key hash\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3\" // For deriving public key hash\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" + "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n keyType: 2, // ECDSA_HASH160 - only needs data field (public key hash)\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n // No private key needed for ECDSA_HASH160\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" }, "identityTopUp": { "label": "Identity Top Up", diff --git a/packages/wasm-sdk/docs.html b/packages/wasm-sdk/docs.html index 3f5f8f04f94..9fd57f9edf9 100644 --- a/packages/wasm-sdk/docs.html +++ b/packages/wasm-sdk/docs.html @@ -1967,7 +1967,7 @@
Parameters:
Public Keys string (required) -
JSON array of public keys with corresponding private keys. All key types require private keys: ECDSA_SECP256K1 accepts privateKeyHex or privateKeyWIF, BLS12_381 requires privateKeyHex, ECDSA_HASH160 requires privateKeyHex (to derive the public key hash). +
JSON array of public keys. Key requirements: ECDSA_SECP256K1 and BLS12_381 require privateKeyHex or privateKeyWif for signing, ECDSA_HASH160 requires only the data field (base64-encoded 20-byte public key hash). @@ -1990,12 +1990,12 @@
Example
}, { id: 1, - keyType: 2, // ECDSA_HASH160 - requires private key to derive public key hash + keyType: 2, // ECDSA_HASH160 - only needs data field (public key hash) purpose: 0, securityLevel: 2, data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key readOnly: false, - privateKeyHex: "e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3" // For deriving public key hash + // No private key needed for ECDSA_HASH160 } ]); diff --git a/packages/wasm-sdk/docs_manifest.json b/packages/wasm-sdk/docs_manifest.json index d111d84319e..c8217278525 100644 --- a/packages/wasm-sdk/docs_manifest.json +++ b/packages/wasm-sdk/docs_manifest.json @@ -1,5 +1,5 @@ { - "generated_at": "2025-09-03T15:38:54.314693+00:00", + "generated_at": "2025-09-03T17:07:09.628289+00:00", "queries": { "getIdentity": { "category": "identity", diff --git a/packages/wasm-sdk/src/state_transitions/identity/mod.rs b/packages/wasm-sdk/src/state_transitions/identity/mod.rs index 3a11c273e7f..8a4a0d3771e 100644 --- a/packages/wasm-sdk/src/state_transitions/identity/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/identity/mod.rs @@ -27,10 +27,9 @@ impl WasmSdk { /// /// * `asset_lock_proof` - The asset lock proof (transaction hex) /// * `asset_lock_proof_private_key` - The private key that controls the asset lock - /// * `public_keys` - JSON array of public keys to add to the identity. Each key object must include: - /// - All key types require either `privateKeyHex` or `privateKeyWif` field - /// - ECDSA_SECP256K1 and BLS12_381 keys: Used for individual key signatures - /// - ECDSA_HASH160 keys: Used to derive the public key hash (produces empty signatures) + /// * `public_keys` - JSON array of public keys to add to the identity. Each key object requirements: + /// - ECDSA_SECP256K1 and BLS12_381: Require `privateKeyHex` or `privateKeyWif` for signing + /// - ECDSA_HASH160: Accepts either `privateKeyHex` (to derive hash) or `data` field (base64-encoded 20-byte hash) /// /// # Implementation Notes /// @@ -132,34 +131,92 @@ impl WasmSdk { _ => SecurityLevel::HIGH }; - // Get private key - required for signing each key - let private_key_bytes = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { - // Decode private key from hex - let bytes = hex::decode(private_key_hex) - .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; - - if bytes.len() != 32 { - return Err(JsValue::from_str(&format!("Private key must be 32 bytes, got {}", bytes.len()))); + // Handle key data based on key type + let (public_key_data, private_key_bytes) = match key_type { + KeyType::ECDSA_HASH160 => { + // Derive HASH160 data from the private key if provided + if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { + // Decode private key from hex + let bytes = hex::decode(private_key_hex) + .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; + + if bytes.len() != 32 { + return Err(JsValue::from_str(&format!( + "Private key must be 32 bytes, got {}", + bytes.len() + ))); + } + + let mut private_key_array = [0u8; 32]; + private_key_array.copy_from_slice(&bytes); + + // Derive HASH160 public key data from private key using network + let derived_data = key_type + .public_key_data_from_private_key_data(&private_key_array, self.network()) + .map_err(|e| { + JsValue::from_str(&format!( + "Failed to derive ECDSA_HASH160 public key data: {}", + e + )) + })?; + + // HASH160 keys are not used for signing during identity creation + (derived_data, [0u8; 32]) + } else if let Some(data_str) = key_data["data"].as_str() { + let key_data_bytes = dash_sdk::dpp::dashcore::base64::decode(data_str) + .map_err(|e| JsValue::from_str(&format!("Invalid base64 key data: {}", e)))?; + + // Enforce correct HASH160 size (20 bytes). + if key_data_bytes.len() != 20 { + return Err(JsValue::from_str(&format!( + "ECDSA_HASH160 key data must be 20 bytes, got {}", + key_data_bytes.len() + ))); + } + + (key_data_bytes, [0u8; 32]) + } else { + return Err(JsValue::from_str( + "ECDSA_HASH160 requires either 'privateKeyHex' to derive from or 'data' (base64-encoded 20-byte hash)", + )); + } + }, + KeyType::ECDSA_SECP256K1 | KeyType::BLS12_381 => { + // For signing key types, require private key and derive public key data + let private_key_bytes = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { + // Decode private key from hex + let bytes = hex::decode(private_key_hex) + .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; + + if bytes.len() != 32 { + return Err(JsValue::from_str(&format!("Private key must be 32 bytes, got {}", bytes.len()))); + } + + let mut private_key_array = [0u8; 32]; + private_key_array.copy_from_slice(&bytes); + private_key_array + } else if let Some(private_key_wif) = key_data["privateKeyWif"].as_str() { + // Parse WIF format private key + let private_key = PrivateKey::from_wif(private_key_wif) + .map_err(|e| JsValue::from_str(&format!("Invalid WIF private key: {}", e)))?; + private_key.inner.secret_bytes() + } else { + return Err(JsValue::from_str(&format!("{} keys require either privateKeyHex or privateKeyWif", key_type_str))); + }; + + // Derive public key data from private key + let public_key_data = key_type.public_key_data_from_private_key_data( + &private_key_bytes, + self.network() + ).map_err(|e| JsValue::from_str(&format!("Failed to derive public key data: {}", e)))?; + + (public_key_data, private_key_bytes) + }, + _ => { + return Err(JsValue::from_str(&format!("Unsupported key type for identity creation: {}", key_type_str))); } - - let mut private_key_array = [0u8; 32]; - private_key_array.copy_from_slice(&bytes); - private_key_array - } else if let Some(private_key_wif) = key_data["privateKeyWif"].as_str() { - // Parse WIF format private key - let private_key = PrivateKey::from_wif(private_key_wif) - .map_err(|e| JsValue::from_str(&format!("Invalid WIF private key: {}", e)))?; - private_key.inner.secret_bytes() - } else { - return Err(JsValue::from_str("Either privateKeyHex or privateKeyWif must be provided for each key")); }; - // Derive public key data from private key - let public_key_data = key_type.public_key_data_from_private_key_data( - &private_key_bytes, - self.network() - ).map_err(|e| JsValue::from_str(&format!("Failed to derive public key data: {}", e)))?; - // Create the identity public key use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; let public_key = IdentityPublicKey::V0(IdentityPublicKeyV0 { @@ -173,8 +230,10 @@ impl WasmSdk { disabled_at: None, }); - // Add the public key and its private key to the signer - signer.add_key(public_key.clone(), private_key_bytes); + // Add the public key and its private key to the signer (only for signing key types) + if key_type != KeyType::ECDSA_HASH160 { + signer.add_key(public_key.clone(), private_key_bytes); + } identity_public_keys.insert(key_id, public_key); key_id += 1; From 53d1cf0eb30cf40f0306693f8f85c63c1b66a585 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 15:11:02 -0400 Subject: [PATCH 10/13] fix(wasm-sdk): correct SDK example to use string enums and proper field names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change keyType/purpose/securityLevel from numeric to string enums - Fix privateKeyHex to privateKeyWif for ECDSA keys - Update placeholder values to be more realistic - Regenerate documentation from updated api-definitions.json 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/wasm-sdk/AI_REFERENCE.md | 20 ++++++++++---------- packages/wasm-sdk/api-definitions.json | 4 ++-- packages/wasm-sdk/docs.html | 20 ++++++++++---------- packages/wasm-sdk/docs_manifest.json | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/wasm-sdk/AI_REFERENCE.md b/packages/wasm-sdk/AI_REFERENCE.md index 94144b9a7fe..88b569e6107 100644 --- a/packages/wasm-sdk/AI_REFERENCE.md +++ b/packages/wasm-sdk/AI_REFERENCE.md @@ -763,7 +763,7 @@ Parameters: - `assetLockProofPrivateKey` (string, required) - Asset Lock Proof Private Key - WIF format private key - `publicKeys` (string, required) - Public Keys - - JSON array of public keys. Key requirements: ECDSA_SECP256K1 and BLS12_381 require privateKeyHex or privateKeyWif for signing, ECDSA_HASH160 requires only the data field (base64-encoded 20-byte public key hash). + - JSON array of public keys. Key requirements: ECDSA_SECP256K1 requires privateKeyHex or privateKeyWif for signing, BLS12_381 requires privateKeyHex for signing, ECDSA_HASH160 requires either the data field (base64-encoded 20-byte public key hash) or privateKeyHex (produces empty signatures). Example: ```javascript @@ -775,21 +775,21 @@ const assetLockProofPrivateKey = "XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThe const publicKeys = JSON.stringify([ { id: 0, - keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2 - purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc. - securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3 + keyType: "ECDSA_SECP256K1", + purpose: "AUTHENTICATION", + securityLevel: "MASTER", data: "A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ", // Base64-encoded public key readOnly: false, - privateKeyHex: "d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7" // Private key for this public key + privateKeyWif: "XBrZJKcW4ajWVNAU6yP87WQog6CjFnpbqyAKgNTZRqmhYvPgMNV2" }, { id: 1, - keyType: 2, // ECDSA_HASH160 - only needs data field (public key hash) - purpose: 0, - securityLevel: 2, - data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key + keyType: "ECDSA_HASH160", + purpose: "AUTHENTICATION", + securityLevel: "HIGH", + data: "ripemd160hash20bytes1234", // Base64-encoded 20-byte RIPEMD160 hash readOnly: false, - // No private key needed for ECDSA_HASH160 + // ECDSA_HASH160 keys produce empty signatures (not required/used for signing) } ]); diff --git a/packages/wasm-sdk/api-definitions.json b/packages/wasm-sdk/api-definitions.json index 10c98efc3d7..475dfa94375 100644 --- a/packages/wasm-sdk/api-definitions.json +++ b/packages/wasm-sdk/api-definitions.json @@ -1279,10 +1279,10 @@ "type": "string", "label": "Public Keys", "required": true, - "description": "JSON array of public keys. Key requirements: ECDSA_SECP256K1 and BLS12_381 require privateKeyHex or privateKeyWif for signing, ECDSA_HASH160 requires only the data field (base64-encoded 20-byte public key hash)." + "description": "JSON array of public keys. Key requirements: ECDSA_SECP256K1 requires privateKeyHex or privateKeyWif for signing, BLS12_381 requires privateKeyHex for signing, ECDSA_HASH160 requires either the data field (base64-encoded 20-byte public key hash) or privateKeyHex (produces empty signatures)." } ], - "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc.\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyHex: \"d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7\" // Private key for this public key\n },\n {\n id: 1,\n keyType: 2, // ECDSA_HASH160 - only needs data field (public key hash)\n purpose: 0,\n securityLevel: 2,\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n // No private key needed for ECDSA_HASH160\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" + "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockProofPrivateKey = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types and private keys for signing/hashing\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n keyType: \"ECDSA_SECP256K1\",\n purpose: \"AUTHENTICATION\",\n securityLevel: \"MASTER\",\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyWif: \"XBrZJKcW4ajWVNAU6yP87WQog6CjFnpbqyAKgNTZRqmhYvPgMNV2\"\n },\n {\n id: 1,\n keyType: \"ECDSA_HASH160\",\n purpose: \"AUTHENTICATION\",\n securityLevel: \"HIGH\",\n data: \"ripemd160hash20bytes1234\", // Base64-encoded 20-byte RIPEMD160 hash\n readOnly: false,\n // ECDSA_HASH160 keys produce empty signatures (not required/used for signing)\n }\n]);\n\nconst result = await sdk.identityCreate(assetLockProof, assetLockProofPrivateKey, publicKeys);" }, "identityTopUp": { "label": "Identity Top Up", diff --git a/packages/wasm-sdk/docs.html b/packages/wasm-sdk/docs.html index d1cd971ba0e..743fe1b0490 100644 --- a/packages/wasm-sdk/docs.html +++ b/packages/wasm-sdk/docs.html @@ -1967,7 +1967,7 @@
Parameters:
Public Keys string (required) -
JSON array of public keys. Key requirements: ECDSA_SECP256K1 and BLS12_381 require privateKeyHex or privateKeyWif for signing, ECDSA_HASH160 requires only the data field (base64-encoded 20-byte public key hash). +
JSON array of public keys. Key requirements: ECDSA_SECP256K1 requires privateKeyHex or privateKeyWif for signing, BLS12_381 requires privateKeyHex for signing, ECDSA_HASH160 requires either the data field (base64-encoded 20-byte public key hash) or privateKeyHex (produces empty signatures). @@ -1981,21 +1981,21 @@
Example
const publicKeys = JSON.stringify([ { id: 0, - keyType: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2 - purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, etc. - securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3 + keyType: "ECDSA_SECP256K1", + purpose: "AUTHENTICATION", + securityLevel: "MASTER", data: "A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ", // Base64-encoded public key readOnly: false, - privateKeyHex: "d1a9b9c8f0e7c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7" // Private key for this public key + privateKeyWif: "XBrZJKcW4ajWVNAU6yP87WQog6CjFnpbqyAKgNTZRqmhYvPgMNV2" }, { id: 1, - keyType: 2, // ECDSA_HASH160 - only needs data field (public key hash) - purpose: 0, - securityLevel: 2, - data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key + keyType: "ECDSA_HASH160", + purpose: "AUTHENTICATION", + securityLevel: "HIGH", + data: "ripemd160hash20bytes1234", // Base64-encoded 20-byte RIPEMD160 hash readOnly: false, - // No private key needed for ECDSA_HASH160 + // ECDSA_HASH160 keys produce empty signatures (not required/used for signing) } ]); diff --git a/packages/wasm-sdk/docs_manifest.json b/packages/wasm-sdk/docs_manifest.json index c8217278525..77f9529150b 100644 --- a/packages/wasm-sdk/docs_manifest.json +++ b/packages/wasm-sdk/docs_manifest.json @@ -1,5 +1,5 @@ { - "generated_at": "2025-09-03T17:07:09.628289+00:00", + "generated_at": "2025-09-03T19:08:50.598629+00:00", "queries": { "getIdentity": { "category": "identity", From 8a213b9fa88f7a6e46554be93a7b82a1bf188c68 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 15:17:58 -0400 Subject: [PATCH 11/13] fix(wasm-sdk): prevent BLS12_381 keys from accepting WIF format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Separate ECDSA_SECP256K1 and BLS12_381 key handling logic - BLS12_381 keys now only accept privateKeyHex format - Return clear error when WIF is provided for BLS12_381 keys - Maintain ECDSA support for both privateKeyHex and privateKeyWif - Add key-type specific error messages for better debugging 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../src/state_transitions/identity/mod.rs | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/wasm-sdk/src/state_transitions/identity/mod.rs b/packages/wasm-sdk/src/state_transitions/identity/mod.rs index 8a4a0d3771e..2b5f1038647 100644 --- a/packages/wasm-sdk/src/state_transitions/identity/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/identity/mod.rs @@ -181,8 +181,8 @@ impl WasmSdk { )); } }, - KeyType::ECDSA_SECP256K1 | KeyType::BLS12_381 => { - // For signing key types, require private key and derive public key data + KeyType::ECDSA_SECP256K1 => { + // For ECDSA signing keys, support both hex and WIF formats let private_key_bytes = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { // Decode private key from hex let bytes = hex::decode(private_key_hex) @@ -201,14 +201,44 @@ impl WasmSdk { .map_err(|e| JsValue::from_str(&format!("Invalid WIF private key: {}", e)))?; private_key.inner.secret_bytes() } else { - return Err(JsValue::from_str(&format!("{} keys require either privateKeyHex or privateKeyWif", key_type_str))); + return Err(JsValue::from_str("ECDSA_SECP256K1 keys require either privateKeyHex or privateKeyWif")); }; // Derive public key data from private key let public_key_data = key_type.public_key_data_from_private_key_data( &private_key_bytes, self.network() - ).map_err(|e| JsValue::from_str(&format!("Failed to derive public key data: {}", e)))?; + ).map_err(|e| JsValue::from_str(&format!("Failed to derive ECDSA_SECP256K1 public key data: {}", e)))?; + + (public_key_data, private_key_bytes) + }, + KeyType::BLS12_381 => { + // BLS12_381 keys only support hex format (WIF is not valid for BLS keys) + if key_data["privateKeyWif"].is_string() { + return Err(JsValue::from_str("BLS12_381 keys do not support WIF format, use privateKeyHex only")); + } + + let private_key_bytes = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { + // Decode private key from hex + let bytes = hex::decode(private_key_hex) + .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; + + if bytes.len() != 32 { + return Err(JsValue::from_str(&format!("Private key must be 32 bytes, got {}", bytes.len()))); + } + + let mut private_key_array = [0u8; 32]; + private_key_array.copy_from_slice(&bytes); + private_key_array + } else { + return Err(JsValue::from_str("BLS12_381 keys require privateKeyHex")); + }; + + // Derive public key data from private key + let public_key_data = key_type.public_key_data_from_private_key_data( + &private_key_bytes, + self.network() + ).map_err(|e| JsValue::from_str(&format!("Failed to derive BLS12_381 public key data: {}", e)))?; (public_key_data, private_key_bytes) }, From 889ebde24d8bba112adae204ae4ac656f643c18c Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 3 Sep 2025 15:34:58 -0400 Subject: [PATCH 12/13] docs: make comment consistent with implementation --- packages/wasm-sdk/src/state_transitions/identity/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/wasm-sdk/src/state_transitions/identity/mod.rs b/packages/wasm-sdk/src/state_transitions/identity/mod.rs index 2b5f1038647..6538880d095 100644 --- a/packages/wasm-sdk/src/state_transitions/identity/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/identity/mod.rs @@ -28,13 +28,15 @@ impl WasmSdk { /// * `asset_lock_proof` - The asset lock proof (transaction hex) /// * `asset_lock_proof_private_key` - The private key that controls the asset lock /// * `public_keys` - JSON array of public keys to add to the identity. Each key object requirements: - /// - ECDSA_SECP256K1 and BLS12_381: Require `privateKeyHex` or `privateKeyWif` for signing + /// - ECDSA_SECP256K1: Requires `privateKeyHex` or `privateKeyWif` for signing + /// - BLS12_381: Requires `privateKeyHex` for signing (WIF format not supported) /// - ECDSA_HASH160: Accepts either `privateKeyHex` (to derive hash) or `data` field (base64-encoded 20-byte hash) /// /// # Implementation Notes /// /// This function uses SimpleSigner to provide individual signatures for each public key as required. - /// Each ECDSA_SECP256K1 and BLS12_381 key will be signed with its corresponding private key, + /// Each ECDSA_SECP256K1 key will be signed with its corresponding private key (from privateKeyHex or privateKeyWif), + /// and each BLS12_381 key will be signed with its corresponding private key (from privateKeyHex only), /// ensuring unique signatures per key as required by DPP validation. /// /// # Returns From 442a403fa8c99629ad8ad583dece608f501cd28f Mon Sep 17 00:00:00 2001 From: thephez Date: Thu, 4 Sep 2025 13:13:22 -0400 Subject: [PATCH 13/13] test: remove disabled, non-functional test --- packages/wasm-sdk/test/state-transitions.test.mjs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/wasm-sdk/test/state-transitions.test.mjs b/packages/wasm-sdk/test/state-transitions.test.mjs index 72a113f911c..9366a73cba4 100644 --- a/packages/wasm-sdk/test/state-transitions.test.mjs +++ b/packages/wasm-sdk/test/state-transitions.test.mjs @@ -310,18 +310,6 @@ await test('identity_withdraw - requires balance', async () => { } }); -// await test('identity_put - causes panic in WASM', async () => { -// try { -// const result = await wasmSdk.identity_put(sdk); -// throw new Error('Should have panicked'); -// } catch (error) { -// if (error.message.includes('Should have panicked')) { -// throw error; -// } -// console.log(' Expected panic (known issue)'); -// } -// }); - // Document State Transitions describe('Document State Transitions');