aptos-ace-sdk 3.11.1__tar.gz
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.
- aptos_ace_sdk-3.11.1/.gitignore +9 -0
- aptos_ace_sdk-3.11.1/PKG-INFO +111 -0
- aptos_ace_sdk-3.11.1/README.md +96 -0
- aptos_ace_sdk-3.11.1/benchmarks/bench_pairing.py +49 -0
- aptos_ace_sdk-3.11.1/pyproject.toml +26 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/__init__.py +46 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/_internal/__init__.py +3 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/_internal/aptos.py +296 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/_internal/common.py +298 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/_internal/deployment.py +45 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/_internal/discovery.py +208 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/_internal/http.py +22 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/admin_recovery.py +258 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/bcs.py +212 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/decryption.py +670 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/dkg/__init__.py +149 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/dkr/__init__.py +158 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/group/__init__.py +196 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/group/bls12381_pairing.py +639 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/group/bls12381fr.py +32 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/group/bls12381g1.py +453 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/group/bls12381g2.py +452 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/ibe_aptos.py +169 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/known_deployments.py +101 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/network.py +262 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/pedersen_polynomial_commitment/__init__.py +188 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/pke/__init__.py +275 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/pke/_ristretto255.py +162 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/pke/elgamal.py +88 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/pke/elgamal_otp_ristretto255.py +217 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/pke/group.py +131 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/pke/hpke_x25519_chacha20poly1305.py +242 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/py.typed +1 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/result.py +64 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/sig/__init__.py +217 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/sigma_dlog_linear/__init__.py +47 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/t_ibe/__init__.py +535 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/t_ibe/bfibe_bls12381_shortpk_otp_hmac.py +380 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/t_ibe/bfibe_bls12381_shortsig_aead.py +416 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/utils.py +78 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/vrf_aptos.py +328 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/vss/__init__.py +7 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/vss/_scheme_types.py +708 -0
- aptos_ace_sdk-3.11.1/src/ace_sdk/vss/dealing.py +109 -0
- aptos_ace_sdk-3.11.1/tests/__init__.py +2 -0
- aptos_ace_sdk-3.11.1/tests/helpers.py +72 -0
- aptos_ace_sdk-3.11.1/tests/test_admin_recovery.py +146 -0
- aptos_ace_sdk-3.11.1/tests/test_crypto.py +232 -0
- aptos_ace_sdk-3.11.1/tests/test_decryption.py +185 -0
- aptos_ace_sdk-3.11.1/tests/test_public_api.py +247 -0
- aptos_ace_sdk-3.11.1/tests/test_vrf_aptos.py +254 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: aptos-ace-sdk
|
|
3
|
+
Version: 3.11.1
|
|
4
|
+
Summary: Python SDK for ACE (mirrors @aptos-labs/ace-sdk)
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: aptos-sdk>=0.11.0
|
|
8
|
+
Requires-Dist: cryptography>=46.0.0
|
|
9
|
+
Requires-Dist: py-ecc>=8.0.0
|
|
10
|
+
Requires-Dist: pyhpke>=0.6.0
|
|
11
|
+
Requires-Dist: pynacl<2,>=1.5.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# ACE Python SDK
|
|
17
|
+
|
|
18
|
+
Python SDK for ACE (Aptos Confidential Extension). This package mirrors the
|
|
19
|
+
core crypto and wire-format surface of `@aptos-labs/ace-sdk` for Python
|
|
20
|
+
callers.
|
|
21
|
+
|
|
22
|
+
The SDK currently includes:
|
|
23
|
+
|
|
24
|
+
- BCS helpers for the wire formats ACE uses.
|
|
25
|
+
- Scheme-tagged BLS12-381 G1/G2 group wrappers.
|
|
26
|
+
- Feldman/Shamir VSS helpers.
|
|
27
|
+
- Pedersen polynomial commitments, DKG and DKR state decoders.
|
|
28
|
+
- Public-key encryption schemes used by ACE.
|
|
29
|
+
- Scheme-tagged Ed25519 signing helpers for reconstructor/admin flows.
|
|
30
|
+
- Threshold IBE primitives.
|
|
31
|
+
- Known deployment metadata and network state view decoders.
|
|
32
|
+
- Discovery snapshot decoding for the keyless discovery service.
|
|
33
|
+
- Aptos IBE encryption helpers.
|
|
34
|
+
- Worker decryption request helpers for basic and custom flows.
|
|
35
|
+
- Disaster-recovery master-secret reconstruction helpers.
|
|
36
|
+
- Aptos threshold-VRF request, share verification, and output reconstruction helpers.
|
|
37
|
+
|
|
38
|
+
## Install for local development
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m venv .venv
|
|
42
|
+
.venv/bin/python -m pip install -e '.[dev]'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick check
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from ace_sdk import pke
|
|
49
|
+
|
|
50
|
+
encryption_key, decryption_key = pke.keygen()
|
|
51
|
+
ciphertext = pke.encrypt(encryption_key, b"hello ace")
|
|
52
|
+
plaintext = pke.decrypt(decryption_key, ciphertext).unwrap_or_throw("decrypt failed")
|
|
53
|
+
assert plaintext == b"hello ace"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The API follows Python naming conventions (`from_bytes`, `to_hex`,
|
|
57
|
+
`derive_encryption_key`) while preserving ACE's byte-level compatibility with
|
|
58
|
+
the TypeScript SDK.
|
|
59
|
+
|
|
60
|
+
The default PKE scheme is HPKE and does not require native system libraries.
|
|
61
|
+
The legacy `ElGamalOtpRistretto255` scheme requires libsodium >= 1.0.18 with
|
|
62
|
+
the `crypto_core_ristretto255_*` APIs. Install it with your system package
|
|
63
|
+
manager, or set `ACE_SDK_LIBSODIUM_PATH` to the library path.
|
|
64
|
+
|
|
65
|
+
## Admin IBE validation
|
|
66
|
+
|
|
67
|
+
The reconstructed master secret printed by the ACE disaster-recovery flow is a
|
|
68
|
+
32-byte little-endian Fr scalar. Use it to extract a full identity decryption
|
|
69
|
+
key and validate a ciphertext:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from ace_sdk import t_ibe
|
|
73
|
+
|
|
74
|
+
idk = t_ibe.extract(msk_scalar=msk_scalar, identity=label).unwrap_or_throw("extract")
|
|
75
|
+
plaintext = t_ibe.decrypt([idk], ciphertext).unwrap_or_throw("decrypt")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For Aptos app encryption, `ibe_aptos.encrypt` builds the same full decryption
|
|
79
|
+
domain as the TypeScript SDK before t-IBE encryption.
|
|
80
|
+
|
|
81
|
+
## Benchmarks
|
|
82
|
+
|
|
83
|
+
The BLS12-381 pairing implementation is pure Python and intentionally kept out
|
|
84
|
+
of the default test suite. Run its opt-in benchmark with:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python benchmarks/bench_pairing.py --iterations 5
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Aptos custom flow
|
|
91
|
+
|
|
92
|
+
For custom access-control flows, callers provide the application proof payload
|
|
93
|
+
and the PKE keypair that workers should use to encrypt returned IDK shares:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from ace_sdk import ibe_aptos, pke
|
|
97
|
+
|
|
98
|
+
enc_pk, enc_sk = pke.keygen()
|
|
99
|
+
plaintext = ibe_aptos.decrypt_custom_flow(
|
|
100
|
+
ace_deployment=deployment,
|
|
101
|
+
keypair_id=keypair_id,
|
|
102
|
+
chain_id=chain_id,
|
|
103
|
+
module_addr=module_addr,
|
|
104
|
+
module_name="example",
|
|
105
|
+
label=b"object-id",
|
|
106
|
+
enc_pk=enc_pk,
|
|
107
|
+
enc_sk=enc_sk,
|
|
108
|
+
payload=custom_proof_payload,
|
|
109
|
+
ciphertext=ciphertext_bytes,
|
|
110
|
+
).unwrap_or_throw("custom decrypt")
|
|
111
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# ACE Python SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for ACE (Aptos Confidential Extension). This package mirrors the
|
|
4
|
+
core crypto and wire-format surface of `@aptos-labs/ace-sdk` for Python
|
|
5
|
+
callers.
|
|
6
|
+
|
|
7
|
+
The SDK currently includes:
|
|
8
|
+
|
|
9
|
+
- BCS helpers for the wire formats ACE uses.
|
|
10
|
+
- Scheme-tagged BLS12-381 G1/G2 group wrappers.
|
|
11
|
+
- Feldman/Shamir VSS helpers.
|
|
12
|
+
- Pedersen polynomial commitments, DKG and DKR state decoders.
|
|
13
|
+
- Public-key encryption schemes used by ACE.
|
|
14
|
+
- Scheme-tagged Ed25519 signing helpers for reconstructor/admin flows.
|
|
15
|
+
- Threshold IBE primitives.
|
|
16
|
+
- Known deployment metadata and network state view decoders.
|
|
17
|
+
- Discovery snapshot decoding for the keyless discovery service.
|
|
18
|
+
- Aptos IBE encryption helpers.
|
|
19
|
+
- Worker decryption request helpers for basic and custom flows.
|
|
20
|
+
- Disaster-recovery master-secret reconstruction helpers.
|
|
21
|
+
- Aptos threshold-VRF request, share verification, and output reconstruction helpers.
|
|
22
|
+
|
|
23
|
+
## Install for local development
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m venv .venv
|
|
27
|
+
.venv/bin/python -m pip install -e '.[dev]'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick check
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from ace_sdk import pke
|
|
34
|
+
|
|
35
|
+
encryption_key, decryption_key = pke.keygen()
|
|
36
|
+
ciphertext = pke.encrypt(encryption_key, b"hello ace")
|
|
37
|
+
plaintext = pke.decrypt(decryption_key, ciphertext).unwrap_or_throw("decrypt failed")
|
|
38
|
+
assert plaintext == b"hello ace"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The API follows Python naming conventions (`from_bytes`, `to_hex`,
|
|
42
|
+
`derive_encryption_key`) while preserving ACE's byte-level compatibility with
|
|
43
|
+
the TypeScript SDK.
|
|
44
|
+
|
|
45
|
+
The default PKE scheme is HPKE and does not require native system libraries.
|
|
46
|
+
The legacy `ElGamalOtpRistretto255` scheme requires libsodium >= 1.0.18 with
|
|
47
|
+
the `crypto_core_ristretto255_*` APIs. Install it with your system package
|
|
48
|
+
manager, or set `ACE_SDK_LIBSODIUM_PATH` to the library path.
|
|
49
|
+
|
|
50
|
+
## Admin IBE validation
|
|
51
|
+
|
|
52
|
+
The reconstructed master secret printed by the ACE disaster-recovery flow is a
|
|
53
|
+
32-byte little-endian Fr scalar. Use it to extract a full identity decryption
|
|
54
|
+
key and validate a ciphertext:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from ace_sdk import t_ibe
|
|
58
|
+
|
|
59
|
+
idk = t_ibe.extract(msk_scalar=msk_scalar, identity=label).unwrap_or_throw("extract")
|
|
60
|
+
plaintext = t_ibe.decrypt([idk], ciphertext).unwrap_or_throw("decrypt")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For Aptos app encryption, `ibe_aptos.encrypt` builds the same full decryption
|
|
64
|
+
domain as the TypeScript SDK before t-IBE encryption.
|
|
65
|
+
|
|
66
|
+
## Benchmarks
|
|
67
|
+
|
|
68
|
+
The BLS12-381 pairing implementation is pure Python and intentionally kept out
|
|
69
|
+
of the default test suite. Run its opt-in benchmark with:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python benchmarks/bench_pairing.py --iterations 5
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Aptos custom flow
|
|
76
|
+
|
|
77
|
+
For custom access-control flows, callers provide the application proof payload
|
|
78
|
+
and the PKE keypair that workers should use to encrypt returned IDK shares:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from ace_sdk import ibe_aptos, pke
|
|
82
|
+
|
|
83
|
+
enc_pk, enc_sk = pke.keygen()
|
|
84
|
+
plaintext = ibe_aptos.decrypt_custom_flow(
|
|
85
|
+
ace_deployment=deployment,
|
|
86
|
+
keypair_id=keypair_id,
|
|
87
|
+
chain_id=chain_id,
|
|
88
|
+
module_addr=module_addr,
|
|
89
|
+
module_name="example",
|
|
90
|
+
label=b"object-id",
|
|
91
|
+
enc_pk=enc_pk,
|
|
92
|
+
enc_sk=enc_sk,
|
|
93
|
+
payload=custom_proof_payload,
|
|
94
|
+
ciphertext=ciphertext_bytes,
|
|
95
|
+
).unwrap_or_throw("custom decrypt")
|
|
96
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright (c) Aptos Labs
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Small opt-in benchmark for the pure-Python BLS12-381 pairing."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import statistics
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
from ace_sdk.group import bls12381g1, bls12381g2
|
|
12
|
+
from ace_sdk.group.bls12381_pairing import fp12_to_bytes, pairing_from_jacobian
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def measure_pairing(iterations: int) -> list[float]:
|
|
16
|
+
g1 = bls12381g1.g1_generator().pt
|
|
17
|
+
g2 = bls12381g2.g2_generator().pt
|
|
18
|
+
timings_ms: list[float] = []
|
|
19
|
+
for _ in range(iterations):
|
|
20
|
+
start = time.perf_counter()
|
|
21
|
+
result = pairing_from_jacobian(g1, g2)
|
|
22
|
+
fp12_to_bytes(result)
|
|
23
|
+
timings_ms.append((time.perf_counter() - start) * 1000)
|
|
24
|
+
return timings_ms
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> int:
|
|
28
|
+
parser = argparse.ArgumentParser()
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"-n",
|
|
31
|
+
"--iterations",
|
|
32
|
+
type=int,
|
|
33
|
+
default=5,
|
|
34
|
+
help="number of pairing operations to measure",
|
|
35
|
+
)
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
if args.iterations < 1:
|
|
38
|
+
raise ValueError("iterations must be >= 1")
|
|
39
|
+
|
|
40
|
+
measure_pairing(1)
|
|
41
|
+
timings_ms = measure_pairing(args.iterations)
|
|
42
|
+
mean_ms = statistics.fmean(timings_ms)
|
|
43
|
+
print(f"pairing_from_jacobian+fp12_to_bytes: {mean_ms:.2f} ms/op")
|
|
44
|
+
print(f"iterations={args.iterations} min={min(timings_ms):.2f} max={max(timings_ms):.2f}")
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aptos-ace-sdk"
|
|
7
|
+
version = "3.11.1"
|
|
8
|
+
description = "Python SDK for ACE (mirrors @aptos-labs/ace-sdk)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Apache-2.0" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"aptos-sdk>=0.11.0",
|
|
14
|
+
"cryptography>=46.0.0",
|
|
15
|
+
"PyNaCl>=1.5.0,<2",
|
|
16
|
+
"py_ecc>=8.0.0",
|
|
17
|
+
"pyhpke>=0.6.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=8.0.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/ace_sdk"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright (c) Aptos Labs
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""ACE Python SDK public package entrypoint."""
|
|
4
|
+
|
|
5
|
+
from ace_sdk import (
|
|
6
|
+
dkg,
|
|
7
|
+
dkr,
|
|
8
|
+
group,
|
|
9
|
+
known_deployments,
|
|
10
|
+
network,
|
|
11
|
+
pedersen_polynomial_commitment,
|
|
12
|
+
pke,
|
|
13
|
+
sig,
|
|
14
|
+
sigma_dlog_linear,
|
|
15
|
+
t_ibe,
|
|
16
|
+
vrf_aptos,
|
|
17
|
+
vss,
|
|
18
|
+
)
|
|
19
|
+
from ace_sdk import admin_recovery, decryption, ibe_aptos
|
|
20
|
+
from ace_sdk._internal.deployment import AceDeployment
|
|
21
|
+
from ace_sdk._internal.discovery import DiscoveryViewV0
|
|
22
|
+
from ace_sdk._internal.common import ContractID, FullDecryptionDomain
|
|
23
|
+
from ace_sdk.result import Result
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AceDeployment",
|
|
27
|
+
"ContractID",
|
|
28
|
+
"DiscoveryViewV0",
|
|
29
|
+
"FullDecryptionDomain",
|
|
30
|
+
"Result",
|
|
31
|
+
"admin_recovery",
|
|
32
|
+
"decryption",
|
|
33
|
+
"dkg",
|
|
34
|
+
"dkr",
|
|
35
|
+
"group",
|
|
36
|
+
"ibe_aptos",
|
|
37
|
+
"known_deployments",
|
|
38
|
+
"network",
|
|
39
|
+
"pedersen_polynomial_commitment",
|
|
40
|
+
"pke",
|
|
41
|
+
"sig",
|
|
42
|
+
"sigma_dlog_linear",
|
|
43
|
+
"t_ibe",
|
|
44
|
+
"vrf_aptos",
|
|
45
|
+
"vss",
|
|
46
|
+
]
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Copyright (c) Aptos Labs
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Mirrors src/_internal/aptos.ts.
|
|
4
|
+
|
|
5
|
+
Only BCS wire-format (serialize/deserialize) is implemented for every Aptos
|
|
6
|
+
public-key/signature scheme: actual signature *verification* happens
|
|
7
|
+
server-side (Move contract / worker), never in this SDK, so we do not need
|
|
8
|
+
real crypto verification logic here -- just byte-exact encode/decode for
|
|
9
|
+
every variant a wallet might produce (Ed25519, AnyPublicKey/SingleKey
|
|
10
|
+
[Secp256k1, Secp256r1, Keyless, FederatedKeyless], MultiEd25519, MultiKey,
|
|
11
|
+
Keyless, FederatedKeyless).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from aptos_sdk.account_address import AccountAddress
|
|
17
|
+
|
|
18
|
+
from ace_sdk.bcs import (
|
|
19
|
+
Deserializer,
|
|
20
|
+
Serializer,
|
|
21
|
+
deserialize_account_address,
|
|
22
|
+
deserialize_option,
|
|
23
|
+
deserialize_option_str,
|
|
24
|
+
deserialize_vector,
|
|
25
|
+
serialize_account_address,
|
|
26
|
+
serialize_option,
|
|
27
|
+
serialize_vector,
|
|
28
|
+
)
|
|
29
|
+
from ace_sdk.result import Result
|
|
30
|
+
|
|
31
|
+
# == Ed25519 ===================================================================
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Ed25519PublicKey:
|
|
35
|
+
LENGTH = 32
|
|
36
|
+
|
|
37
|
+
def __init__(self, key: bytes) -> None:
|
|
38
|
+
if len(key) != self.LENGTH:
|
|
39
|
+
raise ValueError(f"Ed25519PublicKey length should be {self.LENGTH}")
|
|
40
|
+
self.key = key
|
|
41
|
+
|
|
42
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
43
|
+
serializer.serialize_bytes(self.key)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def deserialize(deserializer: Deserializer) -> "Ed25519PublicKey":
|
|
47
|
+
return Ed25519PublicKey(deserializer.deserialize_bytes())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Ed25519Signature:
|
|
51
|
+
LENGTH = 64
|
|
52
|
+
|
|
53
|
+
def __init__(self, data: bytes) -> None:
|
|
54
|
+
if len(data) != self.LENGTH:
|
|
55
|
+
raise ValueError(f"Ed25519Signature length should be {self.LENGTH}")
|
|
56
|
+
self.data = data
|
|
57
|
+
|
|
58
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
59
|
+
serializer.serialize_bytes(self.data)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def deserialize(deserializer: Deserializer) -> "Ed25519Signature":
|
|
63
|
+
return Ed25519Signature(deserializer.deserialize_bytes())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# == Secp256k1 ==================================================================
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Secp256k1PublicKey:
|
|
70
|
+
LENGTH = 65
|
|
71
|
+
COMPRESSED_LENGTH = 33
|
|
72
|
+
|
|
73
|
+
def __init__(self, key: bytes) -> None:
|
|
74
|
+
if len(key) == self.LENGTH:
|
|
75
|
+
self.key = key
|
|
76
|
+
elif len(key) == self.COMPRESSED_LENGTH:
|
|
77
|
+
import ecdsa
|
|
78
|
+
|
|
79
|
+
point = ecdsa.VerifyingKey.from_string(key, curve=ecdsa.SECP256k1).pubkey.point
|
|
80
|
+
x = point.x().to_bytes(32, "big")
|
|
81
|
+
y = point.y().to_bytes(32, "big")
|
|
82
|
+
self.key = b"\x04" + x + y
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Secp256k1PublicKey length should be {self.LENGTH} or "
|
|
86
|
+
f"{self.COMPRESSED_LENGTH}, received {len(key)}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
90
|
+
serializer.serialize_bytes(self.key)
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def deserialize(deserializer: Deserializer) -> "Secp256k1PublicKey":
|
|
94
|
+
return Secp256k1PublicKey(deserializer.deserialize_bytes())
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Secp256k1Signature:
|
|
98
|
+
LENGTH = 64
|
|
99
|
+
|
|
100
|
+
def __init__(self, data: bytes) -> None:
|
|
101
|
+
if len(data) != self.LENGTH:
|
|
102
|
+
raise ValueError(f"Secp256k1Signature length should be {self.LENGTH}")
|
|
103
|
+
self.data = data
|
|
104
|
+
|
|
105
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
106
|
+
serializer.serialize_bytes(self.data)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def deserialize(deserializer: Deserializer) -> "Secp256k1Signature":
|
|
110
|
+
return Secp256k1Signature(deserializer.deserialize_bytes())
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# == Secp256r1 (WebAuthn / passkeys) ============================================
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Secp256r1PublicKey:
|
|
117
|
+
LENGTH = 65
|
|
118
|
+
COMPRESSED_LENGTH = 33
|
|
119
|
+
|
|
120
|
+
def __init__(self, key: bytes) -> None:
|
|
121
|
+
if len(key) == self.COMPRESSED_LENGTH:
|
|
122
|
+
import ecdsa
|
|
123
|
+
|
|
124
|
+
point = ecdsa.VerifyingKey.from_string(key, curve=ecdsa.NIST256p).pubkey.point
|
|
125
|
+
x = point.x().to_bytes(32, "big")
|
|
126
|
+
y = point.y().to_bytes(32, "big")
|
|
127
|
+
self.key = b"\x04" + x + y
|
|
128
|
+
elif len(key) == self.LENGTH:
|
|
129
|
+
self.key = key
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"Secp256r1PublicKey length should be {self.LENGTH} or "
|
|
133
|
+
f"{self.COMPRESSED_LENGTH}, received {len(key)}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
137
|
+
serializer.serialize_bytes(self.key)
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def deserialize(deserializer: Deserializer) -> "Secp256r1PublicKey":
|
|
141
|
+
return Secp256r1PublicKey(deserializer.deserialize_bytes())
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Secp256r1Signature:
|
|
145
|
+
LENGTH = 64
|
|
146
|
+
|
|
147
|
+
def __init__(self, data: bytes) -> None:
|
|
148
|
+
if len(data) != self.LENGTH:
|
|
149
|
+
raise ValueError(f"Secp256r1Signature length should be {self.LENGTH}")
|
|
150
|
+
self.data = data
|
|
151
|
+
|
|
152
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
153
|
+
serializer.serialize_bytes(self.data)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def deserialize(deserializer: Deserializer) -> "Secp256r1Signature":
|
|
157
|
+
return Secp256r1Signature(deserializer.deserialize_bytes())
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class WebAuthnSignature:
|
|
161
|
+
def __init__(
|
|
162
|
+
self, signature: bytes, authenticator_data: bytes, client_data_json: bytes
|
|
163
|
+
) -> None:
|
|
164
|
+
self.signature = signature
|
|
165
|
+
self.authenticator_data = authenticator_data
|
|
166
|
+
self.client_data_json = client_data_json
|
|
167
|
+
|
|
168
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
169
|
+
serializer.serialize_u32_as_uleb128(0)
|
|
170
|
+
serializer.serialize_bytes(self.signature)
|
|
171
|
+
serializer.serialize_bytes(self.authenticator_data)
|
|
172
|
+
serializer.serialize_bytes(self.client_data_json)
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def deserialize(deserializer: Deserializer) -> "WebAuthnSignature":
|
|
176
|
+
variant_id = deserializer.deserialize_uleb128_as_u32()
|
|
177
|
+
if variant_id != 0:
|
|
178
|
+
raise ValueError(f"Invalid id for WebAuthnSignature: {variant_id}")
|
|
179
|
+
signature = deserializer.deserialize_bytes()
|
|
180
|
+
authenticator_data = deserializer.deserialize_bytes()
|
|
181
|
+
client_data_json = deserializer.deserialize_bytes()
|
|
182
|
+
return WebAuthnSignature(signature, authenticator_data, client_data_json)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# == MultiEd25519 ================================================================
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class MultiEd25519PublicKey:
|
|
189
|
+
MAX_KEYS = 32
|
|
190
|
+
MIN_KEYS = 2
|
|
191
|
+
MIN_THRESHOLD = 1
|
|
192
|
+
|
|
193
|
+
def __init__(self, public_keys: list[Ed25519PublicKey], threshold: int) -> None:
|
|
194
|
+
if not (self.MIN_KEYS <= len(public_keys) <= self.MAX_KEYS):
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"Must have between {self.MIN_KEYS} and {self.MAX_KEYS} public keys"
|
|
197
|
+
)
|
|
198
|
+
if not (self.MIN_THRESHOLD <= threshold <= len(public_keys)):
|
|
199
|
+
raise ValueError(f"Threshold must be between {self.MIN_THRESHOLD} and {len(public_keys)}")
|
|
200
|
+
self.public_keys = public_keys
|
|
201
|
+
self.threshold = threshold
|
|
202
|
+
|
|
203
|
+
def to_bytes_inner(self) -> bytes:
|
|
204
|
+
out = bytearray()
|
|
205
|
+
for k in self.public_keys:
|
|
206
|
+
out += k.key
|
|
207
|
+
out.append(self.threshold)
|
|
208
|
+
return bytes(out)
|
|
209
|
+
|
|
210
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
211
|
+
serializer.serialize_bytes(self.to_bytes_inner())
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def deserialize(deserializer: Deserializer) -> "MultiEd25519PublicKey":
|
|
215
|
+
data = deserializer.deserialize_bytes()
|
|
216
|
+
threshold = data[-1]
|
|
217
|
+
keys: list[Ed25519PublicKey] = []
|
|
218
|
+
i = 0
|
|
219
|
+
while i < len(data) - 1:
|
|
220
|
+
keys.append(Ed25519PublicKey(data[i : i + Ed25519PublicKey.LENGTH]))
|
|
221
|
+
i += Ed25519PublicKey.LENGTH
|
|
222
|
+
return MultiEd25519PublicKey(keys, threshold)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class MultiEd25519Signature:
|
|
226
|
+
def __init__(self, signatures: list[Ed25519Signature], bitmap: bytes) -> None:
|
|
227
|
+
self.signatures = signatures
|
|
228
|
+
self.bitmap = bitmap
|
|
229
|
+
|
|
230
|
+
def to_bytes_inner(self) -> bytes:
|
|
231
|
+
out = bytearray()
|
|
232
|
+
for s in self.signatures:
|
|
233
|
+
out += s.data
|
|
234
|
+
out += self.bitmap
|
|
235
|
+
return bytes(out)
|
|
236
|
+
|
|
237
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
238
|
+
serializer.serialize_bytes(self.to_bytes_inner())
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def deserialize(deserializer: Deserializer) -> "MultiEd25519Signature":
|
|
242
|
+
data = deserializer.deserialize_bytes()
|
|
243
|
+
bitmap = data[-4:]
|
|
244
|
+
signatures: list[Ed25519Signature] = []
|
|
245
|
+
i = 0
|
|
246
|
+
end = len(data) - len(bitmap)
|
|
247
|
+
while i < end:
|
|
248
|
+
signatures.append(Ed25519Signature(data[i : i + Ed25519Signature.LENGTH]))
|
|
249
|
+
i += Ed25519Signature.LENGTH
|
|
250
|
+
return MultiEd25519Signature(signatures, bitmap)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# == Ephemeral (used inside Keyless) ============================================
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class EphemeralPublicKey:
|
|
257
|
+
"""Only Ed25519 variant exists today (EphemeralPublicKeyVariant.Ed25519 = 0)."""
|
|
258
|
+
|
|
259
|
+
VARIANT_ED25519 = 0
|
|
260
|
+
|
|
261
|
+
def __init__(self, public_key: Ed25519PublicKey) -> None:
|
|
262
|
+
if not isinstance(public_key, Ed25519PublicKey):
|
|
263
|
+
raise ValueError(f"Unsupported key for EphemeralPublicKey - {type(public_key)}")
|
|
264
|
+
self.public_key = public_key
|
|
265
|
+
self.variant = self.VARIANT_ED25519
|
|
266
|
+
|
|
267
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
268
|
+
serializer.serialize_u32_as_uleb128(self.VARIANT_ED25519)
|
|
269
|
+
self.public_key.serialize(serializer)
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def deserialize(deserializer: Deserializer) -> "EphemeralPublicKey":
|
|
273
|
+
index = deserializer.deserialize_uleb128_as_u32()
|
|
274
|
+
if index == EphemeralPublicKey.VARIANT_ED25519:
|
|
275
|
+
return EphemeralPublicKey(Ed25519PublicKey.deserialize(deserializer))
|
|
276
|
+
raise ValueError(f"Unknown variant index for EphemeralPublicKey: {index}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class EphemeralSignature:
|
|
280
|
+
VARIANT_ED25519 = 0
|
|
281
|
+
|
|
282
|
+
def __init__(self, signature: Ed25519Signature) -> None:
|
|
283
|
+
if not isinstance(signature, Ed25519Signature):
|
|
284
|
+
raise ValueError(f"Unsupported signature for EphemeralSignature - {type(signature)}")
|
|
285
|
+
self.signature = signature
|
|
286
|
+
|
|
287
|
+
def serialize(self, serializer: Serializer) -> None:
|
|
288
|
+
serializer.serialize_u32_as_uleb128(self.VARIANT_ED25519)
|
|
289
|
+
self.signature.serialize(serializer)
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def deserialize(deserializer: Deserializer) -> "EphemeralSignature":
|
|
293
|
+
index = deserializer.deserialize_uleb128_as_u32()
|
|
294
|
+
if index == EphemeralSignature.VARIANT_ED25519:
|
|
295
|
+
return EphemeralSignature(Ed25519Signature.deserialize(deserializer))
|
|
296
|
+
raise ValueError(f"Unknown variant index for EphemeralSignature: {index}")
|