Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,27 +155,53 @@ internal object IdentityNative {

/**
* DashPay Connect key derivation at the DIP-13 sub-feature paths
* `m/9'/coin'/5'/<subFeature>'/0'/identityId'/leaf'[/purpose']`
* (dashpay/dips#191). Bridges `dash_sdk_derive_connect_key_with_resolver`,
* a pure resolver-keyed derive like the slot derives above.
* `m/9'/coin'/5'/<subFeature>'/0'/identityId'/leaf'[/purpose'/rotation']`
* (dashpay/dips#191). Bridges `dash_sdk_derive_connect_key_v2_with_resolver`,
* a pure resolver-keyed derive like the slot derives above. Renamed from
* `deriveConnectKeyWithResolver` when the rotation argument was added, so a
* stale native library fails with `UnsatisfiedLinkError` instead of
* receiving mismatched arguments.
*
* @param subFeature 6 (session authentication; [leaf] is the request
* id) or 7 (app encryption; [leaf] is the bound contract's id).
* @param identityId the identity's 32-byte id (a DIP-14 hardened child).
* @param leaf the 32-byte leaf (a DIP-14 hardened child).
* @param purpose 0 with sub-feature 6 (no purpose level), or the half
* of an encryption pair with sub-feature 7: 1 ENCRYPTION,
* 2 DECRYPTION. Any other combination throws.
* 2 DECRYPTION.
* @param rotation 0 with sub-feature 6 (no rotation level), or the
* non-negative `rotation'` level with sub-feature 7 (0 for the first
* pair). Any other combination throws.
* @return `[privateKey(32), publicKey(33)]`.
*/
external fun deriveConnectKeyWithResolver(
external fun deriveConnectKeyV2WithResolver(
networkOrd: Int,
walletId: ByteArray,
resolverHandle: Long,
subFeature: Int,
identityId: ByteArray,
leaf: ByteArray,
purpose: Int,
rotation: Int,
): Array<ByteArray>

/**
* Pre-rotation DashPay Connect app encryption key at
* `m/9'/coin'/5'/7'/0'/identityId'/contractId'/purpose'` (no
* `rotation'` level), for recognising keys registered on testnet/devnet
* before DIP-13 gained that level. Bridges
* `dash_sdk_derive_legacy_app_encryption_key_with_resolver`.
*
* @param purpose 1 ENCRYPTION or 2 DECRYPTION; anything else throws.
* @return `[privateKey(32), publicKey(33)]`.
*/
external fun deriveLegacyAppEncryptionKeyWithResolver(
networkOrd: Int,
walletId: ByteArray,
resolverHandle: Long,
identityId: ByteArray,
contractId: ByteArray,
purpose: Int,
): Array<ByteArray>

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ class PlatformWalletManager(

/**
* DIP-13 sub-features DashPay Connect v2 keys live under
* (`m/9'/coin'/5'/<subFeature>'/0'/identityId'/leaf'[/purpose']`),
* (`m/9'/coin'/5'/<subFeature>'/0'/identityId'/leaf'[/purpose'/rotation']`),
* registered by the DIP-13 amendment dashpay/dips#191. Mirror of
* Swift's `ManagedPlatformWallet.ConnectSubFeature`.
*/
Expand All @@ -651,13 +651,13 @@ class PlatformWalletManager(
SESSION_AUTHENTICATION(6),
/**
* App encryption key pair: the leaf is the bound data contract's id;
* the purpose level is a [ConnectKeyPurpose].
* the purpose level is a [ConnectKeyPurpose], followed by a rotation level.
*/
APP_ENCRYPTION(7),
}

/**
* The trailing `purpose'` level of the app-encryption path: the DPP
* The `purpose'` level of the app-encryption path: the DPP
* purpose discriminant of the half being derived. Only these two exist
* in the DIP-13 amendment, so the type makes any other value (in
* particular 0, the FFI's "no purpose level") unrepresentable. Mirror
Expand All @@ -669,15 +669,15 @@ class PlatformWalletManager(
}

/**
* Derive a DashPay Connect key at
* `m/9'/coin'/5'/<subFeature>'/0'/<identityId>'/<leaf>'[/<purpose>']`
* from the wallet's seed, resolved through the mnemonic resolver — the
* Android analog of Swift's `deriveConnectKey(subFeature:identityId:leaf:purpose:)`.
* [identityId] and [leaf] are DIP-14 256-bit hardened children, so no
* wallet-local counter is an input. [ConnectSubFeature.APP_ENCRYPTION]
* requires a [purpose], which picks the half of its pair;
* [ConnectSubFeature.SESSION_AUTHENTICATION] requires null. Any other
* combination throws.
* Derive the DashPay Connect session authentication key at
* `m/9'/coin'/5'/6'/0'/<identityId>'/<leaf>'` from the wallet's seed,
* resolved through the mnemonic resolver — the Android analog of Swift's
* `deriveConnectKey(subFeature:identityId:leaf:network:storage:)`. [identityId]
* and [leaf] (the connect request id) are DIP-14 256-bit hardened
* children, so no wallet-local counter is an input. Only
* [ConnectSubFeature.SESSION_AUTHENTICATION] derives here; app
* encryption keys need a purpose and rotation, so passing
* [ConnectSubFeature.APP_ENCRYPTION] throws.
*
* @return `(privateKey(32), publicKey(33))`. Caller zeroes the private half.
*/
Expand All @@ -686,26 +686,91 @@ class PlatformWalletManager(
subFeature: ConnectSubFeature,
identityId: ByteArray,
leaf: ByteArray,
purpose: ConnectKeyPurpose? = null,
): Pair<ByteArray, ByteArray> = connectKey(identityId, leaf, "leaf") {
org.dashfoundation.dashsdk.ffi.IdentityNative.deriveConnectKeyV2WithResolver(
network.ffiValue,
walletId,
mnemonicResolver.nativeHandle,
subFeature.value,
identityId,
leaf,
0,
0,
)
}

/**
* Derive one half of a DashPay Connect app encryption pair at
* `m/9'/coin'/5'/7'/0'/<identityId>'/<leaf>'/<purpose>'/<rotation>'` —
* the Android analog of Swift's
* `deriveConnectKey(subFeature:identityId:leaf:purpose:rotation:network:storage:)`.
* [leaf] is the bound contract's id; [rotation] is the non-negative
* rotation level, 0 for the first pair. Only
* [ConnectSubFeature.APP_ENCRYPTION] derives here; anything else throws.
* Keys registered before DIP-13 gained the rotation level come from
* [deriveLegacyAppEncryptionKey].
*
* @return `(privateKey(32), publicKey(33))`. Caller zeroes the private half.
*/
suspend fun deriveConnectKey(
walletId: ByteArray,
subFeature: ConnectSubFeature,
identityId: ByteArray,
leaf: ByteArray,
purpose: ConnectKeyPurpose,
rotation: Int,
): Pair<ByteArray, ByteArray> = connectKey(identityId, leaf, "leaf") {
org.dashfoundation.dashsdk.ffi.IdentityNative.deriveConnectKeyV2WithResolver(
network.ffiValue,
walletId,
mnemonicResolver.nativeHandle,
subFeature.value,
identityId,
leaf,
purpose.value,
rotation,
)
}

/**
* Derive one half of a DashPay Connect app encryption pair at the
* pre-rotation layout `m/9'/coin'/5'/7'/0'/<identityId>'/<contractId>'/<purpose>'`.
* Only for recognising keys registered on testnet/devnet before DIP-13
* gained the rotation level; derive new keys with [deriveConnectKey].
* Mirror of Swift's `deriveLegacyAppEncryptionKey`.
*
* @return `(privateKey(32), publicKey(33))`. Caller zeroes the private half.
*/
suspend fun deriveLegacyAppEncryptionKey(
walletId: ByteArray,
identityId: ByteArray,
contractId: ByteArray,
purpose: ConnectKeyPurpose,
): Pair<ByteArray, ByteArray> = connectKey(identityId, contractId, "contractId") {
org.dashfoundation.dashsdk.ffi.IdentityNative.deriveLegacyAppEncryptionKeyWithResolver(
network.ffiValue,
walletId,
mnemonicResolver.nativeHandle,
identityId,
contractId,
purpose.value,
)
}

private suspend fun connectKey(
identityId: ByteArray,
leaf: ByteArray,
leafName: String,
derive: () -> Array<ByteArray>,
): Pair<ByteArray, ByteArray> = teardownGate.opWithCleanupOnCancellation(
// withContext's prompt cancellation can discard a completed derive
// before the caller receives the pair it is documented to wipe; scrub
// the private half on that handoff failure.
cleanup = { pair -> pair.first.fill(0) },
) {
require(identityId.size == 32) { "identityId must be 32 bytes, got ${identityId.size}" }
require(leaf.size == 32) { "leaf must be 32 bytes, got ${leaf.size}" }
val pair = org.dashfoundation.dashsdk.errors.mapNativeErrors {
org.dashfoundation.dashsdk.ffi.IdentityNative.deriveConnectKeyWithResolver(
network.ffiValue,
walletId,
mnemonicResolver.nativeHandle,
subFeature.value,
identityId,
leaf,
purpose?.value ?: 0,
)
}
require(leaf.size == 32) { "$leafName must be 32 bytes, got ${leaf.size}" }
val pair = org.dashfoundation.dashsdk.errors.mapNativeErrors(derive)
check(pair.size == 2) { "connect key derive returned ${pair.size} elements" }
pair[0] to pair[1]
}
Expand Down
Loading
Loading