Drift
Both SDKs compute the same secp256k1 group-order-over-2 constant, used for the canonical low-s signature check in signature verification. In TypeScript it is a public, top-level export const. In Python the equivalent is underscore-prefixed and therefore private-by-convention, with no public counterpart exposed anywhere in the module.
This is the mirror image of already-tracked issue #1477 (EIP-712 schema constants like ORDER_PARAMS_FIELDS/SCHEMAS are private in TS but public in Python) — here the direction is reversed, and the constant in question is a different one (a cryptographic constant for the post-sign verification layer, not a schema-shape constant).
TypeScript SDK
sdks/typescript/pmxt/hosted-typed-data.ts:243-244
export const SECP256K1_HALF_N =
0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0n;
Used at hosted-typed-data.ts:627 (if (sValue > SECP256K1_HALF_N)).
Python SDK
sdks/python/pmxt/_hosted_typeddata.py:43-47
_SECP256K1_N = int(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
16,
)
_SECP256K1_HALF_N = _SECP256K1_N // 2
Used at _hosted_typeddata.py:299 (if s_value > _SECP256K1_HALF_N). No public SECP256K1_HALF_N or SECP256K1_N binding exists in the module.
Note: the values are numerically identical (verified) — this is a visibility/export gap, not a correctness bug. Python's own test suite (sdks/python/tests/test_hosted_typeddata.py:48) has to hand-roll a local copy (SECP256K1_HALF_N = SECP256K1_N // 2) rather than importing the module's version, confirming there is no reusable public binding on the Python side.
Expected
Python should expose SECP256K1_HALF_N (and ideally SECP256K1_N) as public module members, matching TypeScript's export, so downstream consumers doing their own EIP-712/ECDSA tooling have a reusable canonical constant on both SDKs.
Impact
Low severity — doesn't affect wire compatibility or signature validity. It's a genuine public-API asymmetry: TypeScript consumers can import and reuse SECP256K1_HALF_N; Python consumers cannot without reaching into a private, underscore-prefixed name.
Found by automated SDK cross-language drift audit
Drift
Both SDKs compute the same secp256k1 group-order-over-2 constant, used for the canonical low-s signature check in signature verification. In TypeScript it is a public, top-level
export const. In Python the equivalent is underscore-prefixed and therefore private-by-convention, with no public counterpart exposed anywhere in the module.This is the mirror image of already-tracked issue #1477 (EIP-712 schema constants like
ORDER_PARAMS_FIELDS/SCHEMASare private in TS but public in Python) — here the direction is reversed, and the constant in question is a different one (a cryptographic constant for the post-sign verification layer, not a schema-shape constant).TypeScript SDK
sdks/typescript/pmxt/hosted-typed-data.ts:243-244Used at
hosted-typed-data.ts:627(if (sValue > SECP256K1_HALF_N)).Python SDK
sdks/python/pmxt/_hosted_typeddata.py:43-47Used at
_hosted_typeddata.py:299(if s_value > _SECP256K1_HALF_N). No publicSECP256K1_HALF_NorSECP256K1_Nbinding exists in the module.Note: the values are numerically identical (verified) — this is a visibility/export gap, not a correctness bug. Python's own test suite (
sdks/python/tests/test_hosted_typeddata.py:48) has to hand-roll a local copy (SECP256K1_HALF_N = SECP256K1_N // 2) rather than importing the module's version, confirming there is no reusable public binding on the Python side.Expected
Python should expose
SECP256K1_HALF_N(and ideallySECP256K1_N) as public module members, matching TypeScript's export, so downstream consumers doing their own EIP-712/ECDSA tooling have a reusable canonical constant on both SDKs.Impact
Low severity — doesn't affect wire compatibility or signature validity. It's a genuine public-API asymmetry: TypeScript consumers can import and reuse
SECP256K1_HALF_N; Python consumers cannot without reaching into a private, underscore-prefixed name.Found by automated SDK cross-language drift audit