actproof 0.2.0__tar.gz → 0.3.0__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.
- {actproof-0.2.0 → actproof-0.3.0}/CHANGELOG.md +50 -3
- actproof-0.3.0/DEPLOY.md +120 -0
- {actproof-0.2.0 → actproof-0.3.0}/PKG-INFO +2 -2
- actproof-0.3.0/SECURITY.md +90 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/__init__.py +1 -1
- {actproof-0.2.0 → actproof-0.3.0}/actproof/anchor.py +18 -1
- actproof-0.3.0/actproof/py.typed +0 -0
- actproof-0.3.0/actproof/signers/__init__.py +139 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/signers/google_kms.py +290 -107
- actproof-0.3.0/actproof/signers/interface.py +613 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/signers/mnemonic.py +87 -35
- actproof-0.3.0/docs/ANCHOR_BACKENDS.md +129 -0
- actproof-0.3.0/docs/INTEGRATION.md +131 -0
- {actproof-0.2.0 → actproof-0.3.0}/pyproject.toml +11 -3
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_anchor.py +48 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_signers_google_kms.py +11 -1
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_signers_interface.py +4 -1
- actproof-0.3.0/tests/test_signers_v030_policy.py +833 -0
- actproof-0.2.0/actproof/signers/__init__.py +0 -89
- actproof-0.2.0/actproof/signers/interface.py +0 -298
- {actproof-0.2.0 → actproof-0.3.0}/.gitignore +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/LICENSE +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/README.md +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/canonical.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/catalogue.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/cli.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/manifest.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/receipt.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/timestamp.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/actproof/verify.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/docs/STS-STANDARDS-APPLICATION.md +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/__init__.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_canonical.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_catalogue.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_catalogue_v3.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_cli.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_manifest.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_receipt.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_signers_mnemonic.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_timestamp.py +0 -0
- {actproof-0.2.0 → actproof-0.3.0}/tests/test_verify.py +0 -0
|
@@ -12,18 +12,65 @@ release. Once 1.0.0 ships, semantic versioning will be strictly followed.
|
|
|
12
12
|
|
|
13
13
|
### Planned
|
|
14
14
|
|
|
15
|
-
- **v0.
|
|
16
|
-
- **v0.3.0** — Cross-implementation conformance test suite landing.
|
|
15
|
+
- **v0.4.0** — Pluggable anchor backend architecture. Generalize the chain-agnostic substrate (canonicalization, RFC 3161 timestamps, receipt format, verifier) into an `AnchorBackend` protocol with chain-specific implementations (Hedera Consensus Service, Stellar memo-hash, Bitcoin OP_RETURN, Ethereum). See `docs/ANCHOR_BACKENDS.md` for the design intent.
|
|
17
16
|
- **v1.0.0** — API frozen.
|
|
18
17
|
- **v2.0.0** — COSE_Sign1 + SCITT Transparent Statement bridge, once RFC 9943 publishes.
|
|
19
18
|
|
|
19
|
+
## [0.3.0] — 2026-05-17
|
|
20
|
+
|
|
21
|
+
**Security hardening release.** Closes a transaction-validation gap present in v0.2.0. See `SECURITY.md` for the advisory.
|
|
22
|
+
|
|
23
|
+
### Security
|
|
24
|
+
|
|
25
|
+
- **`AlgorandSigner.validate_transaction` now rejects attack-vector fields unconditionally.** `rekey_to`, `close_remainder_to`, `group`, and `lease` are rejected on every transaction. v0.2.0 accepted these fields if the sender, receiver, amount, and note prefix passed validation; a `rekey_to=attacker` payment was therefore accepted as a benign-looking 0-ALGO self-payment. This is the same attack class that drained roughly 3.3 million USD across 25 accounts in the February 2023 MyAlgo wallet incident.
|
|
26
|
+
- **Transaction type restricted to `PaymentTxn`.** v0.2.0 accepted any `algosdk.transaction.Transaction` subclass. v0.3.0 rejects `AssetTransferTxn`, `ApplicationCallTxn`, `KeyregTxn`, and any other non-payment type.
|
|
27
|
+
- **Fee bounded and forced flat.** `txn.fee` must lie in `[ALGORAND_MIN_FEE_MICROALGOS, max_fee_microalgos]` (default 1000 microALGOs). `build_transaction` now forces `flat_fee=True` and `fee=1000` on the copy of `SuggestedParams` it passes to `PaymentTxn`, so per-byte fee rates returned by algod under congestion do not produce a transaction the signer would reject.
|
|
28
|
+
- **Note size bounded.** New constant `ALGORAND_MAX_NOTE_BYTES = 1024` enforced explicitly by the signer before any KMS call.
|
|
29
|
+
- **`GoogleKMSSigner`: fail-closed end-to-end integrity verification.** Every KMS call now requires `response.name == request.name`, `response.verified_data_crc32c is True`, `crc32c(signature) == response.signature_crc32c`, and `len(signature) == 64`. Same pattern for `get_public_key` (`response.name`, `pem_crc32c`). v0.2.0 used `hasattr(...)` guards that silently bypassed missing fields; v0.3.0 raises `RuntimeError` if any required field is missing or invalid. Implementation follows Google's recommended pattern documented at `cloud.google.com/kms/docs/data-integrity-guidelines`.
|
|
30
|
+
- **`_assemble_signed_transaction` validates signature length.** Refuses to wrap a non-64-byte signature into a `SignedTransaction` (Ed25519 signatures are always 64 octets per RFC 8032).
|
|
31
|
+
- **Release workflow now runs the test suite before publishing.** `.github/workflows/release.yml` adds a pytest gate plus wheel smoke-test job that the publish job depends on. A failing test now blocks the PyPI upload.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **`AlgorandSigner.__init__` accepts `allowed_note_prefixes` and `max_fee_microalgos` only.** The earlier v0.3.0 draft had also exposed `require_self_payment` and `require_zero_amount` as configurable booleans; these were removed because disabling them turned the signer into a general-purpose Algorand signing adapter, broader than its stated scope. The strict 0-ALGO self-payment shape is now a non-configurable invariant.
|
|
36
|
+
- **`allowed_note_prefixes` accepts a single `bytes` value.** Passing `allowed_note_prefixes=b"quoruna/v1:"` now works without wrapping in a list. The constructor also raises a clearer `TypeError` when a non-bytes element is found.
|
|
37
|
+
- **`__init_subclass__` walks the full MRO.** Forbidden method names inherited via mixin (`class BadSigner(RawSigningMixin, AlgorandSigner)`) are now caught at class-definition time, not only methods defined directly on the subclass.
|
|
38
|
+
- **`validate_transaction` fails closed on missing `super().__init__()`.** Earlier drafts silently set defaults if a subclass forgot to call the base init. v0.3.0 raises `RuntimeError` listing every missing policy attribute.
|
|
39
|
+
- **GCP KMS protection level wording.** Module and class docstrings no longer claim "HSM-backed" unconditionally. The signer accepts keys with any protection level (`SOFTWARE`, `HSM`, `HSM_SINGLE_TENANT`, `EXTERNAL`); operators who need HSM-residency guarantees should create the key version accordingly.
|
|
40
|
+
- **`GoogleKMSSigner` without `[gcp]` deps.** Previously `actproof.signers.GoogleKMSSigner` was set to `None` if the GCP optional dependencies were missing, producing a confusing `TypeError: 'NoneType' object is not callable` on instantiation. v0.3.0 substitutes a stub subclass that raises a clear `RuntimeError` with the install command.
|
|
41
|
+
- **`tsp-client` dependency loosened to `>=0.2.1,<0.3`.** Downstream applications that pull a slightly newer compatible patch release are no longer blocked by the exact-pin requirement.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `ALGORAND_MIN_FEE_MICROALGOS`, `ALGORAND_DEFAULT_MAX_FEE_MICROALGOS`, `ALGORAND_MAX_NOTE_BYTES` constants on `actproof.signers.interface`.
|
|
46
|
+
- New test module `tests/test_signers_v030_policy.py` with 52 tests covering the rejection paths above plus KMS response-verification fail-closed semantics. Includes a cryptographic-equivalence regression test that confirms v0.3.0 produces byte-identical signatures to v0.2.0 for the original strict-policy use case.
|
|
47
|
+
- Two new tests in `tests/test_anchor.py` for `build_transaction`: confirms the resulting transaction carries `fee=1000` regardless of caller-supplied fee rate, and confirms the caller's `SuggestedParams` object is not mutated.
|
|
48
|
+
- `SECURITY.md` documenting the v0.2.0 gap, the v0.3.0 fix, and the threat model. Now included in the sdist.
|
|
49
|
+
- `docs/ANCHOR_BACKENDS.md` design note for v0.4.0 multi-chain anchor abstraction.
|
|
50
|
+
- `docs/INTEGRATION.md` integration guide for downstream applications consuming actproof.
|
|
51
|
+
- `actproof/py.typed` PEP 561 marker so downstream type-checkers honor inline annotations.
|
|
52
|
+
|
|
53
|
+
### Migration from v0.2.0
|
|
54
|
+
|
|
55
|
+
Existing callers using the strict actproof policy (default constructors, anchoring with `actproof:j{...}` notes, 0-ALGO self-payments, fee 1000) see no behavioural change: the signed bytes are byte-identical. Previously-accepted transactions carrying `rekey_to`, `close_remainder_to`, `group`, `lease`, fees above 1000 microALGOs, or notes longer than 1024 bytes are now rejected with `SignerValidationError`. Audit your transaction-construction code to confirm none of these fields should have been set; for a higher `fee` ceiling during network congestion, pass `max_fee_microalgos=<n>` to the signer constructor.
|
|
56
|
+
|
|
57
|
+
Callers of any earlier v0.3.0 draft that used `require_self_payment=False` or `require_zero_amount=False` need to refactor: those kwargs were removed. If you need to sign other transaction shapes, write your own `AlgorandSigner` subclass and override `validate_transaction`.
|
|
58
|
+
|
|
59
|
+
### Acknowledgements
|
|
60
|
+
|
|
61
|
+
Independent review by ChatGPT (May 2026, three review rounds) flagged the original `rekey_to`/`close_remainder_to` gap, the KMS integrity-check weakening, the MRO walk gap, the configurable-policy footgun, the test-gating mistake, the flat-fee deployment issue, the release-workflow missing test gate, and seven additional release-quality items. All findings are addressed in this release.
|
|
62
|
+
|
|
20
63
|
## [0.2.0] — 2026-05-17
|
|
21
64
|
|
|
65
|
+
Version bump for the initial PyPI publication path. See [0.1.0] notes below for the substrate API description; v0.2.0 was the version that actually shipped to PyPI (the 0.1.0 slot was skipped per immutable-release policy).
|
|
66
|
+
|
|
67
|
+
## [0.1.0] — 2026-05-17
|
|
68
|
+
|
|
22
69
|
**First PyPI release of `actproof`.** This is the inaugural published version of the substrate library under its canonical name. The library is installable via `pip install actproof`.
|
|
23
70
|
|
|
24
71
|
### Project history
|
|
25
72
|
|
|
26
|
-
The code in this release was developed under the working name `openproof` on GitHub. The repository at `github.com/deyan-paroushev/openproof-py` was renamed to `github.com/deyan-paroushev/actproof-py` on 2026-05-17; the old URL auto-redirects to the new one. GitHub tags `v0.1.0` (initial public-API surface) and `v0.1.1` (additive schema v3 support) under the previous repository name are the development history of this code; this `v0.
|
|
73
|
+
The code in this release was developed under the working name `openproof` on GitHub. The repository at `github.com/deyan-paroushev/openproof-py` was renamed to `github.com/deyan-paroushev/actproof-py` on 2026-05-17; the old URL auto-redirects to the new one. GitHub tags `v0.1.0` (initial public-API surface) and `v0.1.1` (additive schema v3 support) under the previous repository name are the development history of this code; this `v0.1.0` on PyPI is the first published release under the canonical name and supersedes both working-name tags.
|
|
27
74
|
|
|
28
75
|
The PyPI namespace under `openproof` is unrelated to this project.
|
|
29
76
|
|
actproof-0.3.0/DEPLOY.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Deployment
|
|
2
|
+
|
|
3
|
+
This is the actproof-py v0.1.0 source tree, ready to push to
|
|
4
|
+
`github.com/deyan-paroushev/actproof-py`.
|
|
5
|
+
|
|
6
|
+
## What's inside
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
actproof-py/
|
|
10
|
+
├── README.md Project README
|
|
11
|
+
├── CHANGELOG.md Full v0.0.1 → v0.1.0 history
|
|
12
|
+
├── LICENSE MIT
|
|
13
|
+
├── DEPLOY.md This file
|
|
14
|
+
├── pyproject.toml Hatchling build, dependencies pinned
|
|
15
|
+
├── .gitignore
|
|
16
|
+
├── actproof/ Library source (~3,500 lines, 10 modules)
|
|
17
|
+
│ ├── __init__.py
|
|
18
|
+
│ ├── canonical.py RFC 8785 JCS, strict-mode discipline
|
|
19
|
+
│ ├── manifest.py Manifest envelope (issuer/claim/evidence/recipients)
|
|
20
|
+
│ ├── catalogue.py actproof-events loader + validator
|
|
21
|
+
│ ├── receipt.py Public Receipt + private IssuerEvidence
|
|
22
|
+
│ ├── timestamp.py RFC 3161 with QTSP failover chain
|
|
23
|
+
│ ├── anchor.py ARC-2 disclosed-mode notes on Algorand
|
|
24
|
+
│ ├── signers/ AlgorandSigner ABC + Mnemonic + GoogleKMS
|
|
25
|
+
│ ├── verify.py Six-check end-to-end verification
|
|
26
|
+
│ └── cli.py Click-based CLI (anchor / verify / validate)
|
|
27
|
+
├── tests/ 11 test files, 443 tests, all passing
|
|
28
|
+
│ └── ...
|
|
29
|
+
└── docs/
|
|
30
|
+
└── STS-STANDARDS-APPLICATION.md Maintainer application narrative
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## First push to GitHub
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd actproof-py
|
|
37
|
+
|
|
38
|
+
git init
|
|
39
|
+
git add -A
|
|
40
|
+
git commit -m "Initial release: actproof-py v0.1.0
|
|
41
|
+
|
|
42
|
+
Library and CLI for anchoring signed JSON manifests to Algorand mainnet
|
|
43
|
+
with RFC 3161 qualified timestamps, plus an independent verifier for
|
|
44
|
+
anyone's anchored receipts.
|
|
45
|
+
|
|
46
|
+
10 modules, 443 passing tests, MIT licensed.
|
|
47
|
+
|
|
48
|
+
Implements RFC 8785 JCS, RFC 3161 TSP, Algorand ARC-2 disclosed-mode
|
|
49
|
+
notes, and tracks draft-ietf-scitt-architecture for the v2 COSE_Sign1
|
|
50
|
+
bridge once RFC 9943 publishes."
|
|
51
|
+
|
|
52
|
+
git branch -M main
|
|
53
|
+
git remote add origin git@github.com:deyan-paroushev/actproof-py.git
|
|
54
|
+
git push -u origin main
|
|
55
|
+
|
|
56
|
+
git tag -a v0.1.0 -m "v0.1.0: first usable release"
|
|
57
|
+
git push origin v0.1.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Local verification
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python -m venv .venv
|
|
64
|
+
. .venv/bin/activate
|
|
65
|
+
pip install -e ".[dev,gcp]"
|
|
66
|
+
|
|
67
|
+
# Sanity checks
|
|
68
|
+
actproof --version # actproof, version 0.1.0
|
|
69
|
+
python -c "import actproof; print(actproof.__version__)"
|
|
70
|
+
|
|
71
|
+
# Full test suite — expect 443 passed (with ACTPROOF_EVENTS_ROOT set, see Notes below)
|
|
72
|
+
ACTPROOF_EVENTS_ROOT=/path/to/actproof-events python -m pytest tests/ -q
|
|
73
|
+
|
|
74
|
+
# CLI help
|
|
75
|
+
actproof --help
|
|
76
|
+
actproof anchor --help
|
|
77
|
+
actproof verify --help
|
|
78
|
+
actproof validate --help
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Notes for the catalogue
|
|
82
|
+
|
|
83
|
+
Several tests and the `actproof validate` command need the
|
|
84
|
+
actproof-events catalogue to be present somewhere resolvable.
|
|
85
|
+
|
|
86
|
+
The library (CLI flag, env var, error):
|
|
87
|
+
|
|
88
|
+
1. The `--catalogue` CLI flag (a path to the `acts/` directory)
|
|
89
|
+
2. The `ACTPROOF_CATALOGUE_PATH` environment variable
|
|
90
|
+
3. Otherwise raises `CatalogueLoadError`
|
|
91
|
+
|
|
92
|
+
The test suite (env var, sibling repo, skip):
|
|
93
|
+
|
|
94
|
+
1. The `ACTPROOF_EVENTS_ROOT` environment variable (a path to the
|
|
95
|
+
actproof-events repo root, not the `acts/` directory)
|
|
96
|
+
2. Falls back to `../actproof-events` as a sibling of this repo
|
|
97
|
+
3. Tests that need the real catalogue skip cleanly if neither is
|
|
98
|
+
available; tests using synthetic `tmp_path` catalogues run regardless
|
|
99
|
+
|
|
100
|
+
For local development against `github.com/deyan-paroushev/actproof-events`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Clone as a sibling of this repo
|
|
104
|
+
git clone https://github.com/deyan-paroushev/actproof-events.git ../actproof-events
|
|
105
|
+
|
|
106
|
+
# Or set the env vars explicitly
|
|
107
|
+
export ACTPROOF_EVENTS_ROOT=/path/to/actproof-events # tests
|
|
108
|
+
export ACTPROOF_CATALOGUE_PATH=$ACTPROOF_EVENTS_ROOT/catalogue/acts # library/CLI
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## STS Standards Network application
|
|
112
|
+
|
|
113
|
+
The maintainer's statement of practice is at
|
|
114
|
+
`docs/STS-STANDARDS-APPLICATION.md`. It is written against the four
|
|
115
|
+
sections of the Sovereign Tech Standards Network application form
|
|
116
|
+
(maintainer profile, standards relation, twelve-month plan, track
|
|
117
|
+
record) plus a conflicts-and-disclosures appendix.
|
|
118
|
+
|
|
119
|
+
It is structured to be read by the application reviewer; nothing in it
|
|
120
|
+
is load-bearing for the library itself.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: actproof
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Verifiable receipts of regulated acts. Canonical JSON (RFC 8785), RFC 3161 trusted timestamps, Algorand ARC-2 anchoring, independent verification.
|
|
5
5
|
Project-URL: Homepage, https://github.com/deyan-paroushev/actproof-py
|
|
6
6
|
Project-URL: Documentation, https://github.com/deyan-paroushev/actproof-py/tree/main/docs
|
|
@@ -54,7 +54,7 @@ Requires-Dist: cryptography<46.0,>=42.0
|
|
|
54
54
|
Requires-Dist: py-algorand-sdk<3.0,>=2.6.1
|
|
55
55
|
Requires-Dist: requests<3.0,>=2.32.0
|
|
56
56
|
Requires-Dist: rfc8785<0.2,>=0.1.4
|
|
57
|
-
Requires-Dist: tsp-client
|
|
57
|
+
Requires-Dist: tsp-client<0.3,>=0.2.1
|
|
58
58
|
Provides-Extra: dev
|
|
59
59
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
60
60
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Security advisory: actproof v0.2.0
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
actproof v0.2.0 (released to PyPI on 17 May 2026) contains a
|
|
6
|
+
transaction-validation gap in `actproof.signers.AlgorandSigner.validate_transaction`.
|
|
7
|
+
The signer accepts Algorand transactions that carry `rekey_to`,
|
|
8
|
+
`close_remainder_to`, arbitrary `fee`, `group`, or `lease` fields, as
|
|
9
|
+
long as the sender, receiver, amount, and note prefix pass validation.
|
|
10
|
+
|
|
11
|
+
An attacker who can construct an `algosdk.transaction.Transaction` and
|
|
12
|
+
pass it to `sign_transaction` can therefore have the signer authorise a
|
|
13
|
+
transaction that looks like a benign 0-ALGO self-payment but actually
|
|
14
|
+
transfers signing authority of the account to an address the attacker
|
|
15
|
+
controls (`rekey_to`), drains the account's remaining balance
|
|
16
|
+
(`close_remainder_to`), or burns ALGO via an inflated `fee`.
|
|
17
|
+
|
|
18
|
+
The Algorand `rekey_to` field is the same attack class that drained
|
|
19
|
+
roughly 3.3 million USD across approximately 25 accounts in the
|
|
20
|
+
February 2023 MyAlgo wallet incident.
|
|
21
|
+
|
|
22
|
+
## Severity
|
|
23
|
+
|
|
24
|
+
High. Exploitation requires the attacker to have the ability to
|
|
25
|
+
construct and submit a transaction to the signer (i.e. application-
|
|
26
|
+
layer compromise above the signer). The KMS key itself remains
|
|
27
|
+
protected by the HSM. However, the signer is the trust boundary the
|
|
28
|
+
package advertises, so the gap weakens the security guarantee the
|
|
29
|
+
package was meant to provide.
|
|
30
|
+
|
|
31
|
+
## Affected versions
|
|
32
|
+
|
|
33
|
+
- `actproof` 0.2.0 (the only public release as of this advisory)
|
|
34
|
+
|
|
35
|
+
## Fixed in
|
|
36
|
+
|
|
37
|
+
- `actproof` 0.3.0
|
|
38
|
+
|
|
39
|
+
## Mitigation if upgrading is not immediate
|
|
40
|
+
|
|
41
|
+
Audit all call sites that pass a `Transaction` object to
|
|
42
|
+
`AlgorandSigner.sign_transaction`. Verify that every `Transaction`
|
|
43
|
+
passed is constructed by code you control and that no path constructs
|
|
44
|
+
a `Transaction` with `rekey_to`, `close_remainder_to`, `group`, or
|
|
45
|
+
`lease` set.
|
|
46
|
+
|
|
47
|
+
## Fix in v0.3.0
|
|
48
|
+
|
|
49
|
+
`validate_transaction` now rejects:
|
|
50
|
+
|
|
51
|
+
1. Any transaction that is not a `PaymentTxn` (rejects
|
|
52
|
+
`AssetTransferTxn`, `ApplicationCallTxn`, `KeyregTxn`, and other
|
|
53
|
+
transaction classes).
|
|
54
|
+
2. Any transaction with `rekey_to` set.
|
|
55
|
+
3. Any transaction with `close_remainder_to` set.
|
|
56
|
+
4. Any transaction with `group` set.
|
|
57
|
+
5. Any transaction with `lease` set.
|
|
58
|
+
6. Any transaction with `fee` outside
|
|
59
|
+
`[ALGORAND_MIN_FEE_MICROALGOS, max_fee_microalgos]` (default range:
|
|
60
|
+
exactly 1000 microALGO).
|
|
61
|
+
|
|
62
|
+
The two booleans `require_self_payment` and `require_zero_amount`
|
|
63
|
+
that earlier drafts of v0.3.0 added as configurable kwargs were
|
|
64
|
+
removed. The strict actproof anchoring shape (0-ALGO self-payment,
|
|
65
|
+
tagged note, no rekey/close/group/lease) is now non-configurable.
|
|
66
|
+
|
|
67
|
+
The only remaining configurable policy parameters are
|
|
68
|
+
`allowed_note_prefixes` (the legitimate point of variation between
|
|
69
|
+
actproof, Quoruna, and other downstream anchoring schemes) and
|
|
70
|
+
`max_fee_microalgos` (so callers can anchor during network congestion
|
|
71
|
+
with an explicit, auditable decision).
|
|
72
|
+
|
|
73
|
+
## GCP KMS hardening
|
|
74
|
+
|
|
75
|
+
In addition to the transaction-policy fix, v0.3.0 implements Google's
|
|
76
|
+
recommended end-to-end integrity verification pattern for KMS
|
|
77
|
+
responses, fail-closed on every check. See
|
|
78
|
+
`actproof/signers/google_kms.py` and the v0.3.0 changelog entry.
|
|
79
|
+
|
|
80
|
+
## Acknowledgements
|
|
81
|
+
|
|
82
|
+
Independent review by ChatGPT (May 2026) flagged the
|
|
83
|
+
`rekey_to`/`close_remainder_to` gap and the KMS integrity-check
|
|
84
|
+
weakening. Both findings are addressed in v0.3.0.
|
|
85
|
+
|
|
86
|
+
## Contact
|
|
87
|
+
|
|
88
|
+
For security questions, open a GitHub issue at
|
|
89
|
+
https://github.com/deyan-paroushev/actproof-py or email the address
|
|
90
|
+
in `pyproject.toml`.
|
|
@@ -78,6 +78,7 @@ API
|
|
|
78
78
|
from __future__ import annotations
|
|
79
79
|
|
|
80
80
|
import base64
|
|
81
|
+
import copy
|
|
81
82
|
import logging
|
|
82
83
|
import time
|
|
83
84
|
from dataclasses import dataclass
|
|
@@ -95,6 +96,7 @@ from actproof.receipt import (
|
|
|
95
96
|
ARC2_NOTE_FORMAT,
|
|
96
97
|
AnchorRecord,
|
|
97
98
|
)
|
|
99
|
+
from actproof.signers.interface import ALGORAND_MIN_FEE_MICROALGOS
|
|
98
100
|
|
|
99
101
|
|
|
100
102
|
logger = logging.getLogger(__name__)
|
|
@@ -341,9 +343,24 @@ def build_transaction(
|
|
|
341
343
|
"""
|
|
342
344
|
_ensure_algosdk()
|
|
343
345
|
note = build_note_bytes(manifest_hash, batching_profile)
|
|
346
|
+
|
|
347
|
+
# Force the anchoring transaction to use a flat minimum fee.
|
|
348
|
+
# ChatGPT v0.3.0 review Finding 2: without this, algod's
|
|
349
|
+
# suggested_params can carry flat_fee=False and a per-byte fee
|
|
350
|
+
# rate. The SDK then computes a transaction fee as
|
|
351
|
+
# fee_per_byte * tx_size, which for a ~400-byte anchoring
|
|
352
|
+
# transaction at moderate congestion easily exceeds the signer's
|
|
353
|
+
# max_fee_microalgos cap (default 1000) and would be rejected at
|
|
354
|
+
# validation time. We mutate a copy of the params so callers who
|
|
355
|
+
# supplied their own SuggestedParams object are not surprised by
|
|
356
|
+
# in-place changes.
|
|
357
|
+
sp = copy.copy(suggested_params)
|
|
358
|
+
sp.flat_fee = True
|
|
359
|
+
sp.fee = ALGORAND_MIN_FEE_MICROALGOS
|
|
360
|
+
|
|
344
361
|
return PaymentTxn(
|
|
345
362
|
sender=signer_address,
|
|
346
|
-
sp=
|
|
363
|
+
sp=sp,
|
|
347
364
|
receiver=signer_address,
|
|
348
365
|
amt=0,
|
|
349
366
|
note=note,
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Deyan Paroushev
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
Signer implementations for actproof anchoring.
|
|
5
|
+
|
|
6
|
+
A signer holds an Algorand Ed25519 private key (or a reference to one
|
|
7
|
+
in external custody) and signs transactions built by
|
|
8
|
+
``actproof.anchor``. Every concrete signer subclasses
|
|
9
|
+
``AlgorandSigner`` so the contract ("transactions only, never raw
|
|
10
|
+
bytes") is structurally enforced.
|
|
11
|
+
|
|
12
|
+
What's in this package
|
|
13
|
+
----------------------
|
|
14
|
+
|
|
15
|
+
* ``AlgorandSigner`` (in ``interface.py``) - the abstract base class.
|
|
16
|
+
Concrete signers MUST subclass it. ``__init_subclass__`` raises
|
|
17
|
+
``TypeError`` if a subclass exposes any method whose name suggests
|
|
18
|
+
raw-byte signing, whether defined directly or inherited via a
|
|
19
|
+
mixin (the full forbidden-name list is the module constant
|
|
20
|
+
``FORBIDDEN_METHOD_NAMES``).
|
|
21
|
+
|
|
22
|
+
* ``MnemonicSigner`` (in ``mnemonic.py``) - holds an Algorand
|
|
23
|
+
mnemonic in process memory and signs locally. **Testing only.**
|
|
24
|
+
Emits a loud ``UserWarning`` on construction. Production keys
|
|
25
|
+
belong in an HSM-grade KMS.
|
|
26
|
+
|
|
27
|
+
* ``GoogleKMSSigner`` (in ``google_kms.py``) - production-grade
|
|
28
|
+
signer for users who hold their Ed25519 key in Google Cloud KMS.
|
|
29
|
+
KMS supports Ed25519 natively via the ``EC_SIGN_ED25519``
|
|
30
|
+
algorithm; the private key never leaves the KMS service. For
|
|
31
|
+
HSM-residency guarantees, use a key with protection level ``HSM``
|
|
32
|
+
or ``HSM_SINGLE_TENANT``. Requires the optional ``[gcp]`` install
|
|
33
|
+
extra (``pip install 'actproof[gcp]'``).
|
|
34
|
+
|
|
35
|
+
What's NOT in this package
|
|
36
|
+
--------------------------
|
|
37
|
+
|
|
38
|
+
AWS KMS does not support Ed25519 directly (only RSA and SEC ECC
|
|
39
|
+
curves P-256/P-384/P-521 plus secp256k1). AWS users with HSM-grade
|
|
40
|
+
requirements need either AWS CloudHSM (where Ed25519 is supported)
|
|
41
|
+
or an envelope-encryption pattern. Either path is out-of-scope here;
|
|
42
|
+
AWS users implement their own ``AlgorandSigner`` subclass against
|
|
43
|
+
their preferred backend.
|
|
44
|
+
|
|
45
|
+
Azure Key Vault and HashiCorp Vault similarly: write your own
|
|
46
|
+
subclass against their SDKs. The ABC's enforcement does the security
|
|
47
|
+
work regardless of backend.
|
|
48
|
+
|
|
49
|
+
Example
|
|
50
|
+
-------
|
|
51
|
+
|
|
52
|
+
::
|
|
53
|
+
|
|
54
|
+
from actproof.signers import MnemonicSigner
|
|
55
|
+
from actproof import anchor_manifest, AnchorMode
|
|
56
|
+
|
|
57
|
+
signer = MnemonicSigner("...25 words separated by spaces...")
|
|
58
|
+
record = anchor_manifest(
|
|
59
|
+
manifest_hash,
|
|
60
|
+
signer=signer,
|
|
61
|
+
mode=AnchorMode.DEMO, # testnet
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
For production with GCP KMS::
|
|
65
|
+
|
|
66
|
+
from actproof.signers import GoogleKMSSigner
|
|
67
|
+
|
|
68
|
+
signer = GoogleKMSSigner(
|
|
69
|
+
kms_resource_name=(
|
|
70
|
+
"projects/my-project/locations/europe-west4/"
|
|
71
|
+
"keyRings/anchoring/cryptoKeys/anchor-signer-v1/"
|
|
72
|
+
"cryptoKeyVersions/1"
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
from __future__ import annotations
|
|
78
|
+
|
|
79
|
+
from actproof.signers.interface import (
|
|
80
|
+
ALGORAND_DEFAULT_MAX_FEE_MICROALGOS,
|
|
81
|
+
ALGORAND_MAX_NOTE_BYTES,
|
|
82
|
+
ALGORAND_MIN_FEE_MICROALGOS,
|
|
83
|
+
FORBIDDEN_METHOD_NAMES,
|
|
84
|
+
AlgorandSigner,
|
|
85
|
+
SignerValidationError,
|
|
86
|
+
)
|
|
87
|
+
from actproof.signers.mnemonic import MnemonicSigner
|
|
88
|
+
|
|
89
|
+
# Optional: GoogleKMSSigner requires google-cloud-kms. Import is
|
|
90
|
+
# best-effort; if the GCP libraries are not installed, expose a stub
|
|
91
|
+
# class that raises a clear RuntimeError on instantiation rather than
|
|
92
|
+
# silently substituting None. ChatGPT v0.3.0 review Finding 5: the
|
|
93
|
+
# previous None fallback caused
|
|
94
|
+
# ``TypeError: 'NoneType' object is not callable`` for library users,
|
|
95
|
+
# which is poor UX.
|
|
96
|
+
try:
|
|
97
|
+
from actproof.signers.google_kms import GoogleKMSSigner
|
|
98
|
+
_GOOGLE_KMS_AVAILABLE: bool = True
|
|
99
|
+
_GOOGLE_KMS_ERROR: str | None = None
|
|
100
|
+
except Exception as exc: # noqa: BLE001
|
|
101
|
+
_GOOGLE_KMS_AVAILABLE = False
|
|
102
|
+
_GOOGLE_KMS_ERROR = str(exc)
|
|
103
|
+
|
|
104
|
+
class GoogleKMSSigner(AlgorandSigner): # type: ignore[no-redef]
|
|
105
|
+
"""Stub raised when ``actproof[gcp]`` is not installed.
|
|
106
|
+
|
|
107
|
+
Instantiating this stub fails fast with a clear installation
|
|
108
|
+
hint. The real ``GoogleKMSSigner`` is imported from
|
|
109
|
+
``actproof.signers.google_kms`` when the optional dependencies
|
|
110
|
+
are present.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, *args: object, **kwargs: object) -> None:
|
|
114
|
+
raise RuntimeError(
|
|
115
|
+
"GoogleKMSSigner requires the optional GCP "
|
|
116
|
+
"dependencies (google-cloud-kms, google-crc32c, "
|
|
117
|
+
"cryptography). Install them with:\n\n"
|
|
118
|
+
" pip install 'actproof[gcp]'\n\n"
|
|
119
|
+
f"Underlying import error: {_GOOGLE_KMS_ERROR}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def address(self) -> str: # pragma: no cover
|
|
124
|
+
raise RuntimeError("GoogleKMSSigner stub has no address.")
|
|
125
|
+
|
|
126
|
+
def sign_transaction(self, txn: object) -> object: # pragma: no cover
|
|
127
|
+
raise RuntimeError("GoogleKMSSigner stub cannot sign.")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
__all__ = [
|
|
131
|
+
"AlgorandSigner",
|
|
132
|
+
"MnemonicSigner",
|
|
133
|
+
"GoogleKMSSigner",
|
|
134
|
+
"FORBIDDEN_METHOD_NAMES",
|
|
135
|
+
"SignerValidationError",
|
|
136
|
+
"ALGORAND_MIN_FEE_MICROALGOS",
|
|
137
|
+
"ALGORAND_DEFAULT_MAX_FEE_MICROALGOS",
|
|
138
|
+
"ALGORAND_MAX_NOTE_BYTES",
|
|
139
|
+
]
|