mplang-nightly 0.1.dev264__py3-none-any.whl → 0.1.dev266__py3-none-any.whl

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.
@@ -614,6 +614,101 @@ def kem_derive_impl(
614
614
  return SymmetricKeyValue(suite=suite, key_bytes=secret)
615
615
 
616
616
 
617
+ @crypto.hkdf_p.def_impl
618
+ def hkdf_impl(
619
+ interpreter: Interpreter,
620
+ op: Operation,
621
+ secret: SymmetricKeyValue | TensorValue,
622
+ ) -> SymmetricKeyValue:
623
+ """HKDF key derivation implementation using SHA-256.
624
+
625
+ Implements RFC 5869 HKDF with HMAC-SHA256. This is the NIST SP 800-56C
626
+ compliant way to derive symmetric keys from ECDH shared secrets.
627
+
628
+ Current implementation supports only SHA-256. Future versions will add
629
+ SHA-512, SHA3-256, and BLAKE2b support.
630
+
631
+ Security Notes:
632
+ - Uses salt=None (defaults to 32-byte all-zero salt per RFC 5869)
633
+ - ONLY SAFE for high-entropy IKM (e.g., 256-bit ECDH shared secrets)
634
+ - NOT suitable for: passwords, low-entropy secrets, or repeated key derivations
635
+ - For session keys with same ECDH pair: use unique 'info' per session
636
+
637
+ Per NIST SP 800-56C Rev. 2:
638
+ "If the IKM is already cryptographically strong (e.g., from ECDH),
639
+ a salt may not be necessary, but using one does not hurt."
640
+
641
+ Args:
642
+ interpreter: Runtime interpreter context
643
+ op: Operation node containing attributes (info, hash_algo)
644
+ secret: Input key material (IKM) as SymmetricKeyValue or TensorValue
645
+ Must be high-entropy (≥256 bits) for security with salt=None
646
+
647
+ Returns:
648
+ SymmetricKeyValue with suite="hkdf-{hash_algo}" and 32-byte key_bytes
649
+
650
+ Raises:
651
+ TypeError: If secret is not SymmetricKeyValue or TensorValue
652
+ ValueError: If info parameter is empty (required for domain separation)
653
+ NotImplementedError: If hash_algo is not "sha256"
654
+ """
655
+ from cryptography.hazmat.primitives import hashes
656
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
657
+
658
+ # Extract operation attributes
659
+ info_str = op.attrs.get("info", "")
660
+ hash_algo = (
661
+ op.attrs.get("hash_algo", "sha256").lower().replace("-", "").replace("_", "")
662
+ )
663
+
664
+ # Validate info parameter (REQUIRED for domain separation per NIST)
665
+ if not info_str:
666
+ raise ValueError(
667
+ "HKDF requires non-empty 'info' parameter for domain separation. "
668
+ "The info string binds the derived key to a specific protocol/context. "
669
+ "Recommended format: 'namespace/component/purpose/version'"
670
+ )
671
+
672
+ info_bytes = info_str.encode("utf-8")
673
+
674
+ # Extract input key material (IKM) bytes
675
+ if isinstance(secret, SymmetricKeyValue):
676
+ ikm = secret.key_bytes
677
+ elif isinstance(secret, TensorValue):
678
+ ikm = secret.unwrap().tobytes()
679
+ else:
680
+ raise TypeError(
681
+ f"hkdf secret must be SymmetricKeyValue or TensorValue, "
682
+ f"got {type(secret).__name__}"
683
+ )
684
+
685
+ # Validate hash algorithm (currently only SHA-256 implemented)
686
+ if hash_algo != "sha256":
687
+ raise NotImplementedError(
688
+ f"HKDF with hash algorithm '{hash_algo}' is not yet implemented. "
689
+ f"Currently only 'sha256' is supported. "
690
+ f"Planned future support: sha512, sha3256, blake2b"
691
+ )
692
+
693
+ # Perform HKDF derivation using cryptography library
694
+ # Note: salt=None uses 32-byte all-zero salt (not random salt!)
695
+ # This is secure ONLY because ECDH outputs are already high-entropy (256-bit uniform)
696
+ # For low-entropy inputs or repeated derivations, a random salt would be required
697
+ hkdf = HKDF(
698
+ algorithm=hashes.SHA256(),
699
+ length=32, # Output length in bytes (AES-256 key = 32 bytes)
700
+ salt=None, # 32-byte zero salt (secure for high-entropy ECDH shared secrets)
701
+ info=info_bytes, # Context-specific binding for domain separation
702
+ )
703
+
704
+ derived_key = hkdf.derive(ikm)
705
+
706
+ # Return SymmetricKeyValue with composite suite name
707
+ # Format: "hkdf-{hash_algo}" to indicate derivation method and hash function
708
+ suite = f"hkdf-{hash_algo}"
709
+ return SymmetricKeyValue(suite=suite, key_bytes=derived_key)
710
+
711
+
617
712
  @crypto.random_bytes_p.def_impl
618
713
  def random_bytes_impl(interpreter: Interpreter, op: Operation) -> TensorValue:
619
714
  """Generate random bytes using os.urandom."""
@@ -199,6 +199,9 @@ select_p = el.Primitive[el.Object]("crypto.select")
199
199
  kem_keygen_p = el.Primitive[tuple[el.Object, el.Object]]("crypto.kem_keygen")
200
200
  kem_derive_p = el.Primitive[el.Object]("crypto.kem_derive")
201
201
 
202
+ # HKDF (Key Derivation Function)
203
+ hkdf_p = el.Primitive[el.Object]("crypto.hkdf")
204
+
202
205
  # Randomness
203
206
  random_bytes_p = el.Primitive[el.Object]("crypto.random_bytes")
204
207
 
@@ -336,6 +339,41 @@ def _kem_derive_ae(
336
339
  return SymmetricKeyType(suite)
337
340
 
338
341
 
342
+ @hkdf_p.def_abstract_eval
343
+ def _hkdf_ae(
344
+ secret: elt.BaseType, *, info: str, hash_algo: str = "sha256"
345
+ ) -> SymmetricKeyType:
346
+ """Abstract evaluation for HKDF key derivation.
347
+
348
+ Args:
349
+ secret: Input key material (SymmetricKeyType from kem_derive or TensorType[u8])
350
+ info: Context string for domain separation (required, non-empty, keyword-only)
351
+ hash_algo: Hash algorithm in lowercase without hyphens (e.g., "sha256", keyword-only)
352
+
353
+ Returns:
354
+ SymmetricKeyType with suite="hkdf-{hash_algo}"
355
+
356
+ Raises:
357
+ TypeError: If info or hash_algo is not a string
358
+ ValueError: If info is empty (required for domain separation per NIST)
359
+ """
360
+ # Validate info and hash_algo at trace time
361
+ if not isinstance(info, str) or not info:
362
+ raise ValueError(
363
+ "HKDF requires non-empty 'info' parameter for domain separation. "
364
+ "The info string binds the derived key to a specific protocol/context. "
365
+ "Recommended format: 'namespace/component/purpose/version'"
366
+ )
367
+ if not isinstance(hash_algo, str) or not hash_algo:
368
+ raise TypeError("hash_algo must be a non-empty string")
369
+
370
+ # Normalize: lowercase, no hyphens
371
+ hash_algo_normalized = hash_algo.lower().replace("-", "").replace("_", "")
372
+
373
+ # Return SymmetricKeyType with composite suite indicating derivation method
374
+ return SymmetricKeyType(suite=f"hkdf-{hash_algo_normalized}")
375
+
376
+
339
377
  @random_bytes_p.def_abstract_eval
340
378
  def _random_bytes_ae(length: int) -> elt.TensorType:
341
379
  return elt.TensorType(elt.u8, (length,))
@@ -472,6 +510,69 @@ def kem_derive(private_key: el.Object, public_key: el.Object) -> el.Object:
472
510
  return kem_derive_p.bind(private_key, public_key)
473
511
 
474
512
 
513
+ def hkdf(secret: el.Object, info: str, *, hash_algo: str = "sha256") -> el.Object:
514
+ """Derive a cryptographic key from input key material using HKDF.
515
+
516
+ HKDF (HMAC-based Key Derivation Function) is specified in RFC 5869 and
517
+ required by NIST SP 800-56C Rev.2 for deriving symmetric keys from
518
+ key agreement schemes like ECDH. Per NIST: "The shared secret output
519
+ from a key-agreement scheme SHALL NOT be used directly as a cryptographic
520
+ key. A key-derivation function (KDF) SHALL be used."
521
+
522
+ Args:
523
+ secret: Input key material (IKM). Accepts:
524
+ - SymmetricKeyValue: Typically from crypto.kem_derive (ECDH output)
525
+ - TensorType[u8, (N,)]: Raw bytes (N-byte secret)
526
+ info: Application-specific context string for domain separation.
527
+ REQUIRED and must be non-empty. Different info values produce
528
+ cryptographically independent keys even from the same secret.
529
+ Recommended format: "namespace/component/purpose/version"
530
+ Example: "mplang/device/tee/v2"
531
+ hash_algo: Hash function to use. Must be lowercase without hyphens.
532
+ Currently supported: "sha256" (default)
533
+ Future support planned: "sha512", "sha3256", "blake2b"
534
+ Default "sha256" provides 128-bit security level.
535
+
536
+ Returns:
537
+ SymmetricKeyValue with:
538
+ - suite: "hkdf-{hash_algo}" (e.g., "hkdf-sha256")
539
+ - key_bytes: 32-byte derived key suitable for AES-256-GCM
540
+
541
+ Security considerations:
542
+ - Output length: Fixed at 32 bytes (256 bits) for AES-256 keys
543
+ - Salt: Uses salt=None (acceptable for ECDH output per NIST guidance)
544
+ - Info: Provides protocol/context binding (domain separation)
545
+ - Deterministic: Same (secret, info, hash_algo) always produces same key
546
+
547
+ Raises:
548
+ ValueError:
549
+ - At abstract evaluation time if hash_algo is unsupported.
550
+ - At execution time if info is empty.
551
+ NotImplementedError:
552
+ - At execution time if hash_algo is not "sha256".
553
+
554
+ Examples:
555
+ >>> # Standard TEE session establishment
556
+ >>> sk_local, pk_local = crypto.kem_keygen("x25519")
557
+ >>> sk_remote, pk_remote = crypto.kem_keygen("x25519")
558
+ >>> # ECDH on both sides
559
+ >>> shared_local = crypto.kem_derive(sk_local, pk_remote)
560
+ >>> shared_remote = crypto.kem_derive(sk_remote, pk_local)
561
+ >>> # HKDF for domain separation
562
+ >>> sess_local = crypto.hkdf(shared_local, "mplang/device/tee/v2")
563
+ >>> sess_remote = crypto.hkdf(shared_remote, "mplang/device/tee/v2")
564
+ >>> # sess_local and sess_remote have identical key_bytes
565
+ >>> # but suite="hkdf-sha256" (not "x25519")
566
+ >>>
567
+ >>> # Derive multiple independent keys from one master secret
568
+ >>> master_secret = crypto.kem_derive(sk, pk)
569
+ >>> encryption_key = crypto.hkdf(master_secret, "app/encryption/v1")
570
+ >>> mac_key = crypto.hkdf(master_secret, "app/mac/v1")
571
+ >>> # encryption_key ≠ mac_key due to different info strings
572
+ """
573
+ return hkdf_p.bind(secret, info=info, hash_algo=hash_algo)
574
+
575
+
475
576
  def random_bytes(length: int) -> el.Object:
476
577
  """Generate cryptographically secure random bytes at runtime.
477
578
 
@@ -61,6 +61,9 @@ DEVICE_ATTR_NAME = "__device__"
61
61
  # Default KEM suite for TEE session establishment
62
62
  _TEE_KEM_SUITE: str = "x25519"
63
63
 
64
+ # HKDF info string for TEE session key derivation (domain separation)
65
+ _TEE_HKDF_INFO: str = "mplang/device/tee/v2"
66
+
64
67
  # Global cache for TEE sessions (keyed by (frm_dev_id, to_dev_id))
65
68
  # Each entry is (context_id, sess_frm, sess_tee) where context_id ensures
66
69
  # sessions are not reused across different trace/interp contexts.
@@ -412,16 +415,24 @@ def _ensure_tee_session(
412
415
  """Ensure a TEE session (sess_frm at sender, sess_tee at TEE) exists.
413
416
 
414
417
  Performs remote attestation and establishes an encrypted channel between
415
- a PPU and a TEE device. The session keys are cached to avoid repeated
416
- handshakes within the same execution context.
418
+ a PPU and a TEE device using NIST SP 800-56C compliant key derivation.
419
+ Session keys are cached within the same execution context to avoid
420
+ repeated handshakes.
417
421
 
418
- The protocol:
422
+ Protocol (ECDH + Remote Attestation + HKDF):
419
423
  1. TEE generates keypair (sk, pk) and creates attestation quote binding pk
420
- 2. Quote is sent to the sender (PPU) for verification
424
+ 2. Quote is sent to sender (PPU) for verification
421
425
  3. Sender verifies quote and extracts TEE's attested public key
422
- 4. Sender generates its own ephemeral keypair and sends pk to TEE
423
- 5. Both sides derive shared secret using ECDH (X25519)
424
- 6. The shared secret is used directly as the session key for AES-GCM
426
+ 4. Sender generates ephemeral keypair and sends pk to TEE
427
+ 5. Both sides derive ECDH shared secret using X25519
428
+ 6. Both sides derive session keys from shared secret using HKDF-SHA256
429
+ with protocol-specific info string for domain separation
430
+
431
+ Security properties:
432
+ - Remote attestation: TEE identity is cryptographically verified
433
+ - Ephemeral keys: Perfect forward secrecy (keys not reused across sessions)
434
+ - HKDF derivation: NIST SP 800-56C compliant (shared secret not used directly)
435
+ - Domain separation: Info parameter binds keys to TEE protocol v2
425
436
 
426
437
  Args:
427
438
  frm_dev_id: Source device ID (PPU)
@@ -462,10 +473,18 @@ def _ensure_tee_session(
462
473
  v_sk, v_pk = simp.pcall_static((frm_rank,), crypto.kem_keygen, _TEE_KEM_SUITE)
463
474
  v_pk_at_tee = simp.shuffle_static(v_pk, {tee_rank: frm_rank})
464
475
 
465
- # 5. Both sides derive shared secret (symmetric key) via ECDH
466
- # The shared secret from X25519 ECDH is suitable for direct use as AES key
467
- sess_frm = simp.pcall_static((frm_rank,), crypto.kem_derive, v_sk, tee_pk_at_sender)
468
- sess_tee = simp.pcall_static((tee_rank,), crypto.kem_derive, tee_sk, v_pk_at_tee)
476
+ # 5. Both sides derive ECDH shared secret using X25519
477
+ # Note: kem_derive signature is (private_key, public_key) - suite is in key type
478
+ shared_frm = simp.pcall_static(
479
+ (frm_rank,), crypto.kem_derive, v_sk, tee_pk_at_sender
480
+ )
481
+ shared_tee = simp.pcall_static((tee_rank,), crypto.kem_derive, tee_sk, v_pk_at_tee)
482
+
483
+ # 6. Derive session keys using HKDF-SHA256 for domain separation
484
+ # Per NIST SP 800-56C: "shared secret SHALL NOT be used directly as a key"
485
+ # HKDF provides: uniform distribution + protocol-specific context binding
486
+ sess_frm = simp.pcall_static((frm_rank,), crypto.hkdf, shared_frm, _TEE_HKDF_INFO)
487
+ sess_tee = simp.pcall_static((tee_rank,), crypto.hkdf, shared_tee, _TEE_HKDF_INFO)
469
488
 
470
489
  # Cache the session
471
490
  _tee_session_cache[key] = (current_context_id, sess_frm, sess_tee)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mplang-nightly
3
- Version: 0.1.dev264
3
+ Version: 0.1.dev266
4
4
  Summary: Multi-Party Programming Language
5
5
  Author-email: SecretFlow Team <secretflow-contact@service.alipay.com>
6
6
  License: Apache License
@@ -82,7 +82,7 @@ mplang/v2/cli.py,sha256=QtiTFG418k26opRy4GhVV8fwFqRS11xTLH3xRCIIm6M,19665
82
82
  mplang/v2/cli_guide.md,sha256=kyoCaqkvIJJ1vsvCyBu3qgOuRSb0txu9BDZoy9GU5S0,3617
83
83
  mplang/v2/backends/__init__.py,sha256=H-4-jBEPWBZl6XT7AxBShRINnruF_f_2lB4iaiQoXME,1988
84
84
  mplang/v2/backends/bfv_impl.py,sha256=cQPinze3c2xN4CmIIoXxZoIEhu9ynoGaXbdF95z_aTE,25709
85
- mplang/v2/backends/crypto_impl.py,sha256=-4-ry7rXgPjkuM-KTFc4pG3k-O5dSNTEkcMR4W2LtJg,19327
85
+ mplang/v2/backends/crypto_impl.py,sha256=tU0KdI34hnYvYujKUkiA1XYpvy4lo_MKpidt0NSIKlk,23205
86
86
  mplang/v2/backends/field_impl.py,sha256=50sKGOlkUiaTj_IAola86uQeoi-fxV0o7G91BdTCWZA,14788
87
87
  mplang/v2/backends/func_impl.py,sha256=R0662cC0gSSfkjuLyevJ_g4bJDJirY76LTFYqEimCkE,3585
88
88
  mplang/v2/backends/phe_impl.py,sha256=r836e_qBHGrHhfnFail5IaUDzvS7bABjdEQmJmAtBVI,4127
@@ -106,7 +106,7 @@ mplang/v2/backends/simp_worker/ops.py,sha256=DMQCsKeoMtemy5ozsVZt2eoF8NZlhLeHZMD
106
106
  mplang/v2/backends/simp_worker/state.py,sha256=eRUI7MP6gU8KPC9-H5fwcoAPKOsfW2ODWvpoKWbecMk,1554
107
107
  mplang/v2/dialects/__init__.py,sha256=hvzAvz6_brfFyDGgKknoPdgh5EY033YNYwotuJK_zoA,1493
108
108
  mplang/v2/dialects/bfv.py,sha256=XrE3FX9DHWqNzUVzY0tuwPvNVVRZYpD51JZIZF-q-l4,22350
109
- mplang/v2/dialects/crypto.py,sha256=rgJh_VO971MR-0D5jryz8osItF0Q4aGYhlEYjV_hBEE,17503
109
+ mplang/v2/dialects/crypto.py,sha256=dH_DtoE3pGAKeOLPHxeyGtXC-nGwBsOs62TKikJEaq0,22197
110
110
  mplang/v2/dialects/dtypes.py,sha256=bGM3Jna3BnvE4MPOurWrEmQegGPxd26z1HIWox1rj0U,12104
111
111
  mplang/v2/dialects/field.py,sha256=6nBJg08k5WHb2o5msr8XAnxMQLpoTej55VQ7iSRnC4o,6380
112
112
  mplang/v2/dialects/func.py,sha256=UlaMof4NEG28VOtiRL7zBRYgFbIX74YTqqgvozbils0,4375
@@ -138,7 +138,7 @@ mplang/v2/kernels/okvs_opt.cpp,sha256=d_HhvMdcebYsG2x7kYzjuFgmEsh9WKLH6SHee3375B
138
138
  mplang/v2/kernels/py_kernels.py,sha256=FDsD86IHV-UBzxZLolhSOkrp24PuboHXeb1gBHLOfMo,12073
139
139
  mplang/v2/libs/collective.py,sha256=pfXq9tmFUNKjeHhWMTjtzOi-m2Fn1lLru1G6txZVyic,10683
140
140
  mplang/v2/libs/device/__init__.py,sha256=mXsSvXrWmlHu6Ch87Vcd85m4L_qdDkbSvJyHyuai2fc,1251
141
- mplang/v2/libs/device/api.py,sha256=8i0KP3q_XbOfPYB9fzs7pA6vVY4Ib6lxdoiwAi_RlmU,27838
141
+ mplang/v2/libs/device/api.py,sha256=d_Wbka8bxxXsRMW6zDhjzL9LPtChSk2-ryfi-c4Mqsk,28830
142
142
  mplang/v2/libs/device/cluster.py,sha256=YUqYZ_IBS6rpV5ejUFP3kTxcTQHSyeDeuaJcsiFY_Js,12508
143
143
  mplang/v2/libs/ml/__init__.py,sha256=xTxhC_YwHP32muUEFCEwOjc-3Ml-vmO48NNECU90zm4,787
144
144
  mplang/v2/libs/ml/sgb.py,sha256=qoJww01EEbWo9nvwB5w-JZqx9BchcmhSrToypxV7fPg,60297
@@ -170,8 +170,8 @@ mplang/v2/runtime/dialect_state.py,sha256=HxO1i4kSOujS2tQzAF9-WmI3nChSaGgupf2_07
170
170
  mplang/v2/runtime/interpreter.py,sha256=UzrM5oepka6H0YKRZncNXhsuwKVm4pliG5J92fFRZMI,32300
171
171
  mplang/v2/runtime/object_store.py,sha256=yT6jtKG2GUEJVmpq3gnQ8mCMvUFYzgBciC5A-J5KRdk,5998
172
172
  mplang/v2/runtime/value.py,sha256=CMOxElJP78v7pjasPhEpbxWbSgB2KsLbpPmzz0mQX0E,4317
173
- mplang_nightly-0.1.dev264.dist-info/METADATA,sha256=lTGfgTEgk6Ptaf1GEFsmMwixspGR58Tb2SrGlT6vJKM,16775
174
- mplang_nightly-0.1.dev264.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
175
- mplang_nightly-0.1.dev264.dist-info/entry_points.txt,sha256=mG1oJT-GAjQR834a62_QIWb7litzWPPyVnwFqm-rWuY,55
176
- mplang_nightly-0.1.dev264.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
177
- mplang_nightly-0.1.dev264.dist-info/RECORD,,
173
+ mplang_nightly-0.1.dev266.dist-info/METADATA,sha256=y4CgWHDl3wbczPz2lcTxnqXsTGFkPcuk3bGcnIQoAV0,16775
174
+ mplang_nightly-0.1.dev266.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
175
+ mplang_nightly-0.1.dev266.dist-info/entry_points.txt,sha256=mG1oJT-GAjQR834a62_QIWb7litzWPPyVnwFqm-rWuY,55
176
+ mplang_nightly-0.1.dev266.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
177
+ mplang_nightly-0.1.dev266.dist-info/RECORD,,