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.
Files changed (41) hide show
  1. {actproof-0.2.0 → actproof-0.3.0}/CHANGELOG.md +50 -3
  2. actproof-0.3.0/DEPLOY.md +120 -0
  3. {actproof-0.2.0 → actproof-0.3.0}/PKG-INFO +2 -2
  4. actproof-0.3.0/SECURITY.md +90 -0
  5. {actproof-0.2.0 → actproof-0.3.0}/actproof/__init__.py +1 -1
  6. {actproof-0.2.0 → actproof-0.3.0}/actproof/anchor.py +18 -1
  7. actproof-0.3.0/actproof/py.typed +0 -0
  8. actproof-0.3.0/actproof/signers/__init__.py +139 -0
  9. {actproof-0.2.0 → actproof-0.3.0}/actproof/signers/google_kms.py +290 -107
  10. actproof-0.3.0/actproof/signers/interface.py +613 -0
  11. {actproof-0.2.0 → actproof-0.3.0}/actproof/signers/mnemonic.py +87 -35
  12. actproof-0.3.0/docs/ANCHOR_BACKENDS.md +129 -0
  13. actproof-0.3.0/docs/INTEGRATION.md +131 -0
  14. {actproof-0.2.0 → actproof-0.3.0}/pyproject.toml +11 -3
  15. {actproof-0.2.0 → actproof-0.3.0}/tests/test_anchor.py +48 -0
  16. {actproof-0.2.0 → actproof-0.3.0}/tests/test_signers_google_kms.py +11 -1
  17. {actproof-0.2.0 → actproof-0.3.0}/tests/test_signers_interface.py +4 -1
  18. actproof-0.3.0/tests/test_signers_v030_policy.py +833 -0
  19. actproof-0.2.0/actproof/signers/__init__.py +0 -89
  20. actproof-0.2.0/actproof/signers/interface.py +0 -298
  21. {actproof-0.2.0 → actproof-0.3.0}/.gitignore +0 -0
  22. {actproof-0.2.0 → actproof-0.3.0}/LICENSE +0 -0
  23. {actproof-0.2.0 → actproof-0.3.0}/README.md +0 -0
  24. {actproof-0.2.0 → actproof-0.3.0}/actproof/canonical.py +0 -0
  25. {actproof-0.2.0 → actproof-0.3.0}/actproof/catalogue.py +0 -0
  26. {actproof-0.2.0 → actproof-0.3.0}/actproof/cli.py +0 -0
  27. {actproof-0.2.0 → actproof-0.3.0}/actproof/manifest.py +0 -0
  28. {actproof-0.2.0 → actproof-0.3.0}/actproof/receipt.py +0 -0
  29. {actproof-0.2.0 → actproof-0.3.0}/actproof/timestamp.py +0 -0
  30. {actproof-0.2.0 → actproof-0.3.0}/actproof/verify.py +0 -0
  31. {actproof-0.2.0 → actproof-0.3.0}/docs/STS-STANDARDS-APPLICATION.md +0 -0
  32. {actproof-0.2.0 → actproof-0.3.0}/tests/__init__.py +0 -0
  33. {actproof-0.2.0 → actproof-0.3.0}/tests/test_canonical.py +0 -0
  34. {actproof-0.2.0 → actproof-0.3.0}/tests/test_catalogue.py +0 -0
  35. {actproof-0.2.0 → actproof-0.3.0}/tests/test_catalogue_v3.py +0 -0
  36. {actproof-0.2.0 → actproof-0.3.0}/tests/test_cli.py +0 -0
  37. {actproof-0.2.0 → actproof-0.3.0}/tests/test_manifest.py +0 -0
  38. {actproof-0.2.0 → actproof-0.3.0}/tests/test_receipt.py +0 -0
  39. {actproof-0.2.0 → actproof-0.3.0}/tests/test_signers_mnemonic.py +0 -0
  40. {actproof-0.2.0 → actproof-0.3.0}/tests/test_timestamp.py +0 -0
  41. {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.2.0** — `docs/` complete with three worked examples (NIS2 / EUDR / software release), GitHub Action wrapper `release-anchor.yml`, EU Trusted List chain validation for RFC 3161 tokens, stricter verifier semantics (PARTIAL state distinct from PASS), disclosure CLI subcommands (`actproof verify-disclosure`, `actproof issue-disclosure`).
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.2.0` on PyPI is the first published release under the canonical name and supersedes both working-name tags.
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
 
@@ -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.2.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==0.2.1
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`.
@@ -6,7 +6,7 @@ actproof: anchor signed JSON manifests; verify anyone's anchored receipts.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.2.0"
9
+ __version__ = "0.3.0"
10
10
 
11
11
 
12
12
  # ─────────────────────────────────────────────────────────────────
@@ -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=suggested_params,
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
+ ]