diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 728a9efe..d4e6c0a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,6 +171,8 @@ jobs: scripts/test_contract_flows.py \ scripts/check_auction_flows.py \ scripts/test_auction_flows.py \ + scripts/check_wallet_signature_flows.py \ + scripts/test_wallet_signature_flows.py \ scripts/check_release_readiness.py \ scripts/test_release_readiness.py \ scripts/generate_release_manifest.py \ @@ -474,6 +476,8 @@ jobs: python3 scripts/check_contract_flows.py 2>&1 | tee ci-logs/contract-flows-check.log python3 scripts/test_auction_flows.py 2>&1 | tee ci-logs/auction-flows-tests.log python3 scripts/check_auction_flows.py 2>&1 | tee ci-logs/auction-flows-check.log + python3 scripts/test_wallet_signature_flows.py 2>&1 | tee ci-logs/wallet-signature-flows-tests.log + python3 scripts/check_wallet_signature_flows.py 2>&1 | tee ci-logs/wallet-signature-flows-check.log - name: Release readiness shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index bf6c3b12..9f791752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ the release policy in `docs/release-policy.md`. ### Added +- Added INT-004 wallet, EIP-712, ERC-1271, and Safe signing guide with + `docs/integrations/wallets-and-signatures.md`, a checker/test pair, + local/CI gate wiring, release-readiness navigation, and release-manifest + coverage so React, mobile, Electron, operator UI, indexer, and backend + signing-service teams can trace domain fields, replay controls, EOA and + contract-signer behavior, Safe/WalletConnect caveats, failure states, and + no-secret custody boundaries without production-readiness overclaims. - Added INT-003 auction frontend and indexer flow spec with `docs/integrations/auction-flows.md`, a checker/test pair, local/CI gate wiring, release-readiness navigation, and release-manifest coverage so diff --git a/Makefile b/Makefile index d8c1aa8c..47995f4a 100644 --- a/Makefile +++ b/Makefile @@ -26,9 +26,9 @@ RM_RF := rm -rf out cache broadcast endif PATH := $(FOUNDRY_BIN)$(PATH_SEPARATOR)$(REPO_ROOT)/$(VENV_BIN)$(PATH_SEPARATOR)$(PATH) -.PHONY: check build test gas-snapshot gas-snapshot-check size deploy-rehearsal metadata-fixtures-check windows-check-wrapper-policy windows-check-wrapper-runtime drop-authorization-fixtures-check drop-authorization-signing-evidence-check signer-custody-readiness-check admin-ceremony-evidence-check solidity-formatting-check release-artifacts release-artifacts-check source-verification-inputs source-verification-inputs-check abi-compatibility abi-compatibility-check broadcast-manifest-inputs broadcast-manifest-inputs-check deployment-manifests deployment-manifest-check address-books address-books-check dependency-artifacts dependency-artifacts-check ceremony-evidence-check randomizer-operations-check release-signatures-check signed-release-tag-check bytecode-release-proof bytecode-release-proof-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check public-beta-evidence-check public-beta-blocker-report public-beta-blocker-report-check production-release-blocker-report production-release-blocker-report-check release-evidence-packet-index release-evidence-packet-index-check release-evidence-issue-backlog release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-labels-check release-evidence-issue-body-sync release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-report-check release-evidence-live-audit-markdown-check release-evidence-live-audit-archive release-evidence-live-audit-archive-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check release-readiness-check release-manifest release-manifest-check release-checksums release-checksums-check changelog-check fmt-check slither clean +.PHONY: check build test gas-snapshot gas-snapshot-check size deploy-rehearsal metadata-fixtures-check windows-check-wrapper-policy windows-check-wrapper-runtime drop-authorization-fixtures-check drop-authorization-signing-evidence-check signer-custody-readiness-check admin-ceremony-evidence-check solidity-formatting-check release-artifacts release-artifacts-check source-verification-inputs source-verification-inputs-check abi-compatibility abi-compatibility-check broadcast-manifest-inputs broadcast-manifest-inputs-check deployment-manifests deployment-manifest-check address-books address-books-check dependency-artifacts dependency-artifacts-check ceremony-evidence-check randomizer-operations-check release-signatures-check signed-release-tag-check bytecode-release-proof bytecode-release-proof-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check public-beta-evidence-check public-beta-blocker-report public-beta-blocker-report-check production-release-blocker-report production-release-blocker-report-check release-evidence-packet-index release-evidence-packet-index-check release-evidence-issue-backlog release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-labels-check release-evidence-issue-body-sync release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-report-check release-evidence-live-audit-markdown-check release-evidence-live-audit-archive release-evidence-live-audit-archive-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check wallet-signature-flows-check release-readiness-check release-manifest release-manifest-check release-checksums release-checksums-check changelog-check fmt-check slither clean -check: build test gas-snapshot-check size solidity-formatting-check windows-check-wrapper-policy metadata-fixtures-check drop-authorization-fixtures-check drop-authorization-signing-evidence-check signer-custody-readiness-check admin-ceremony-evidence-check release-artifacts-check source-verification-inputs-check abi-compatibility-check signed-release-tag-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check public-beta-evidence-check public-beta-blocker-report-check production-release-blocker-report-check release-evidence-packet-index-check release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-labels-check release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-archive-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check release-readiness-check release-checksums-check changelog-check deploy-rehearsal +check: build test gas-snapshot-check size solidity-formatting-check windows-check-wrapper-policy metadata-fixtures-check drop-authorization-fixtures-check drop-authorization-signing-evidence-check signer-custody-readiness-check admin-ceremony-evidence-check release-artifacts-check source-verification-inputs-check abi-compatibility-check signed-release-tag-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check public-beta-evidence-check public-beta-blocker-report-check production-release-blocker-report-check release-evidence-packet-index-check release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-labels-check release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-archive-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check wallet-signature-flows-check release-readiness-check release-checksums-check changelog-check deploy-rehearsal build: forge build @@ -278,14 +278,18 @@ auction-flows-check: $(PYTHON) scripts/test_auction_flows.py $(PYTHON) scripts/check_auction_flows.py +wallet-signature-flows-check: + $(PYTHON) scripts/test_wallet_signature_flows.py + $(PYTHON) scripts/check_wallet_signature_flows.py + release-readiness-check: $(PYTHON) scripts/test_release_readiness.py $(PYTHON) scripts/check_release_readiness.py -release-manifest: address-books source-verification-inputs dependency-artifacts ceremony-evidence-check randomizer-operations-check release-signatures-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check drop-authorization-signing-evidence-check signer-custody-readiness-check public-beta-evidence-check risk-register public-beta-blocker-report-check production-release-blocker-report-check release-evidence-packet-index-check release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-markdown-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check drop-authorization-fixtures-check release-readiness-check +release-manifest: address-books source-verification-inputs dependency-artifacts ceremony-evidence-check randomizer-operations-check release-signatures-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check drop-authorization-signing-evidence-check signer-custody-readiness-check public-beta-evidence-check risk-register public-beta-blocker-report-check production-release-blocker-report-check release-evidence-packet-index-check release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-markdown-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check wallet-signature-flows-check drop-authorization-fixtures-check release-readiness-check $(PYTHON) scripts/generate_release_manifest.py -release-manifest-check: address-books-check source-verification-inputs-check dependency-artifacts-check ceremony-evidence-check randomizer-operations-check release-signatures-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check drop-authorization-signing-evidence-check signer-custody-readiness-check public-beta-evidence-check risk-register-check public-beta-blocker-report-check production-release-blocker-report-check release-evidence-packet-index-check release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-markdown-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check drop-authorization-fixtures-check release-readiness-check +release-manifest-check: address-books-check source-verification-inputs-check dependency-artifacts-check ceremony-evidence-check randomizer-operations-check release-signatures-check non-local-release-evidence-check external-audit-report-evidence-check fork-deployment-rehearsal-evidence-check testnet-deployment-rehearsal-evidence-check drop-authorization-signing-evidence-check signer-custody-readiness-check public-beta-evidence-check risk-register-check public-beta-blocker-report-check production-release-blocker-report-check release-evidence-packet-index-check release-evidence-issue-backlog-check release-evidence-issue-links-check release-evidence-issue-body-sync-check release-evidence-issue-bodies-check release-evidence-issue-closure-check release-evidence-live-audit-markdown-check architecture-threat-model-check audit-package-check incident-response-check integrations-readme-check contract-flows-check auction-flows-check wallet-signature-flows-check drop-authorization-fixtures-check release-readiness-check $(PYTHON) scripts/test_release_manifest.py $(PYTHON) scripts/generate_release_manifest.py --check diff --git a/docs/drop-authorization-signing.md b/docs/drop-authorization-signing.md index a69011d0..088cdd68 100644 --- a/docs/drop-authorization-signing.md +++ b/docs/drop-authorization-signing.md @@ -16,6 +16,9 @@ the target-state Solidity coverage lives in [`test/StreamDropsEIP712.t.sol`](../test/StreamDropsEIP712.t.sol), [`test/StreamDropsERC1271.t.sol`](../test/StreamDropsERC1271.t.sol), and [`test/helpers/DropAuthTestHelper.sol`](../test/helpers/DropAuthTestHelper.sol). +For app-facing wallet, Safe, WalletConnect, frontend preflight, and UX failure +handling, use +[`docs/integrations/wallets-and-signatures.md`](integrations/wallets-and-signatures.md). This guide covers: @@ -317,4 +320,5 @@ Required cross-links: - [`docs/audit-package.md`](audit-package.md) - [`docs/incident-response.md`](incident-response.md) - [`docs/signer-custody-readiness.md`](signer-custody-readiness.md) +- [`docs/integrations/wallets-and-signatures.md`](integrations/wallets-and-signatures.md) - [`ops/ROADMAP.md`](../ops/ROADMAP.md) diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 58206f6a..a6feab30 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -34,12 +34,12 @@ Supported consumer categories for this entrypoint: | Consumer | Current entrypoint | Status | | --- | --- | --- | -| React web app | Use the generated address books, ABI surface/checksum artifacts, signing docs, metadata docs, release-readiness dashboard, [`contract-flows.md`](contract-flows.md), and [`auction-flows.md`](auction-flows.md) | Fixed-price and auction flows are documented; other detailed flow specs remain future `INT` work | -| Mobile app | Use the same contract surface artifacts plus wallet/signing docs, [`contract-flows.md`](contract-flows.md), and [`auction-flows.md`](auction-flows.md) | Fixed-price and auction flows are documented; WalletConnect and deep-link guidance remains future `INT-008` work | -| Electron app | Use web-app artifacts plus strict renderer/process security assumptions | Entry point only; Electron security guidance remains future `INT-009` work | +| React web app | Use the generated address books, ABI surface/checksum artifacts, signing docs, metadata docs, release-readiness dashboard, [`contract-flows.md`](contract-flows.md), [`auction-flows.md`](auction-flows.md), and [`wallets-and-signatures.md`](wallets-and-signatures.md) | Fixed-price, auction, and wallet/signature flows are documented; other detailed flow specs remain future `INT` work | +| Mobile app | Use the same contract surface artifacts plus [`wallets-and-signatures.md`](wallets-and-signatures.md), [`contract-flows.md`](contract-flows.md), and [`auction-flows.md`](auction-flows.md) | Fixed-price, auction, WalletConnect, and mobile handoff signature guidance are documented; deeper mobile reference architecture remains future `INT-008` work | +| Electron app | Use web-app artifacts plus [`wallets-and-signatures.md`](wallets-and-signatures.md) and strict renderer/process security assumptions | Signature boundary guidance is documented; deeper Electron shell guidance remains future `INT-009` work | | Indexer | Use event topic catalog, interface IDs, deployment manifests, address books, release manifest, and [`auction-flows.md`](auction-flows.md) | Auction lifecycle reconstruction is documented; full event replay spec remains future `INT-005` work | | Operator UI | Use deployment docs, ceremony evidence, randomizer operations docs, risk register, and release-readiness dashboard | Entry point only; dashboard query model remains future `GOV`/`INT` work | -| Backend signing service | Use EIP-712, ERC-1271, signer custody, and drop authorization signing docs | Local templates only; production signing evidence remains blocked | +| Backend signing service | Use EIP-712, ERC-1271, Safe, signer custody, drop authorization signing docs, and [`wallets-and-signatures.md`](wallets-and-signatures.md) | Local templates and integration guidance only; production signing evidence remains blocked | ## Source Of Truth @@ -70,6 +70,7 @@ Use tracked generated artifacts rather than hand-maintained copies. | Drop signing | [`docs/drop-authorization-signing.md`](../drop-authorization-signing.md) | EIP-712 and ERC-1271 local fixture guidance | | Fixed-price mint flow | [`docs/integrations/contract-flows.md`](contract-flows.md) | Current `INT-002` transaction, event, credit, and failure-state guide | | Auction flow | [`docs/integrations/auction-flows.md`](auction-flows.md) | Current `INT-003` auction submit, bid, settlement, credit, pause, and indexer guide | +| Wallet and signature guide | [`docs/integrations/wallets-and-signatures.md`](wallets-and-signatures.md) | Current `INT-004` EIP-712, ERC-1271, Safe, WalletConnect, backend signer, and failure-state guide | | Release signatures | [`docs/release-signatures.md`](../release-signatures.md) | No production signatures are committed | ## Canonical Artifacts @@ -91,6 +92,7 @@ can prove the entrypoint keeps all required local targets reachable: - [`docs/known-blockers.md`](../known-blockers.md) - [`docs/integrations/contract-flows.md`](contract-flows.md) - [`docs/integrations/auction-flows.md`](auction-flows.md) +- [`docs/integrations/wallets-and-signatures.md`](wallets-and-signatures.md) - [`release-artifacts/README.md`](../../release-artifacts/README.md) - [`release-artifacts/contracts.json`](../../release-artifacts/contracts.json) - [`release-artifacts/baselines/v0.1.0/abi-surface.json`](../../release-artifacts/baselines/v0.1.0/abi-surface.json) @@ -123,7 +125,8 @@ to understand what is still intentionally future work: [`contract-flows.md`](contract-flows.md). - `INT-003`: auction frontend and indexer flow spec is now [`auction-flows.md`](auction-flows.md). -- `INT-004`: wallet, EIP-712, ERC-1271, and Safe signing guide. +- `INT-004`: wallet, EIP-712, ERC-1271, and Safe signing guide is now + [`wallets-and-signatures.md`](wallets-and-signatures.md). - `INT-005`: event and indexer reconstruction spec. - `INT-006`: metadata rendering, cache, animation sandbox, and marketplace integration guide. @@ -161,6 +164,8 @@ python scripts/test_integrations_readme.py python scripts/check_integrations_readme.py python scripts/test_auction_flows.py python scripts/check_auction_flows.py +python scripts/test_wallet_signature_flows.py +python scripts/check_wallet_signature_flows.py python scripts/check_release_readiness.py python scripts/check_changelog.py ``` diff --git a/docs/integrations/wallets-and-signatures.md b/docs/integrations/wallets-and-signatures.md new file mode 100644 index 00000000..4004fb79 --- /dev/null +++ b/docs/integrations/wallets-and-signatures.md @@ -0,0 +1,418 @@ +# Wallets And Signatures + +This document is the integration guide for wallet, EIP-712, ERC-1271, Safe, +WalletConnect, and backend signing-service handling around +`StreamDrops.DropAuthorization`. It is a pre-audit local baseline, not +production-ready, and not a security claim. Local evidence does not replace +fork/testnet/live evidence required for public beta or production release. + +Use this with the raw signing schema in +[`docs/drop-authorization-signing.md`](../drop-authorization-signing.md), the +signer custody readiness model in +[`docs/signer-custody-readiness.md`](../signer-custody-readiness.md), the +fixed-price flow in [`docs/integrations/contract-flows.md`](contract-flows.md), +the auction flow in [`docs/integrations/auction-flows.md`](auction-flows.md), +and the release-readiness dashboard in +[`docs/release-readiness.md`](../release-readiness.md). + +## Maturity And Scope + +This guide is for React, mobile, Electron, operator UI, indexer, and backend +signing service teams that need to build or verify wallet-facing signature +flows without guessing from Solidity tests. + +It covers: + +- EIP-712 domain and typed-data shape for `DropAuthorization`; +- replay, expiry, cancellation, and signer epoch boundaries; +- EOA signatures, including 65-byte and EIP-2098 compact signatures; +- ERC-1271 contract-signer behavior; +- Safe signing and validation expectations; +- WalletConnect and mobile handoff caveats; +- backend signing-service and production custody boundaries; +- frontend preflight reads and user-visible failure handling; and +- no-secret validation commands and source-of-truth artifacts. + +It does not provide a production signing service, custody approval, live Safe +configuration, public-beta approval, audited signature UX, or production +deployment evidence. Those remain governed by +[`docs/signer-custody-readiness.md`](../signer-custody-readiness.md), +[`docs/non-local-release-evidence.md`](../non-local-release-evidence.md), +[`docs/public-beta-evidence.md`](../public-beta-evidence.md), and +[`release-artifacts/latest/public-beta-evidence.json`](../../release-artifacts/latest/public-beta-evidence.json). + +## Source Of Truth + +Use tracked committed sources before wiring a wallet or signing service. + +| Need | Source of truth | Integration note | +| --- | --- | --- | +| Integration entrypoint | [`docs/integrations/README.md`](README.md) | Starts frontend, mobile, Electron, indexer, operator UI, and signing-service discovery | +| Raw signing guide | [`docs/drop-authorization-signing.md`](../drop-authorization-signing.md) | Canonical schema, fixtures, evidence template, and operator flow | +| Drop authorization ADR | [`docs/adr/0001-drop-authorization.md`](../adr/0001-drop-authorization.md) | Accepted design decision for typed authorization, replay, cancellation, ERC-1271, and signer epoch | +| Signer custody evidence | [`docs/signer-custody-readiness.md`](../signer-custody-readiness.md) | Production signer custody evidence model | +| Fixed-price transaction flow | [`docs/integrations/contract-flows.md`](contract-flows.md) | `saleMode = 1`, payer/value, recipient, credit, and failure-state guidance | +| Auction transaction flow | [`docs/integrations/auction-flows.md`](auction-flows.md) | `saleMode = 2`, zero payer/recipient/price, auction custody, bid, and settlement guidance | +| Release readiness | [`docs/release-readiness.md`](../release-readiness.md) | Current launch blocker dashboard | +| Non-local evidence policy | [`docs/non-local-release-evidence.md`](../non-local-release-evidence.md) | Retained fork/testnet/live evidence requirements | +| Public beta evidence | [`docs/public-beta-evidence.md`](../public-beta-evidence.md) | Public beta evidence posture | +| Risk register | [`release-artifacts/latest/risk-register.json`](../../release-artifacts/latest/risk-register.json) | Generated blocker and risk source | +| Release manifest | [`release-artifacts/latest/release-manifest.json`](../../release-artifacts/latest/release-manifest.json) | Generated source-of-truth manifest | +| ABI review surface | [`release-artifacts/baselines/v0.1.0/abi-surface.json`](../../release-artifacts/baselines/v0.1.0/abi-surface.json) | Tracked ABI baseline | +| ABI checksums | [`release-artifacts/latest/abi-checksums.json`](../../release-artifacts/latest/abi-checksums.json) | Integrator checksum source | +| Event topic catalog | [`release-artifacts/latest/event-topic-catalog.json`](../../release-artifacts/latest/event-topic-catalog.json) | Indexer event signature source | +| Interface IDs | [`release-artifacts/latest/interface-ids.json`](../../release-artifacts/latest/interface-ids.json) | Interface lookup source | +| Local address book | [`deployments/address-books/anvil-6529stream-v0.1.0-001.json`](../../deployments/address-books/anvil-6529stream-v0.1.0-001.json) | Local development addresses | +| Fork-mainnet address book | [`deployments/address-books/fork-mainnet-6529stream-v0.1.0-001-broadcast.json`](../../deployments/address-books/fork-mainnet-6529stream-v0.1.0-001-broadcast.json) | Retained fork rehearsal addresses | +| Contract implementation | [`smart-contracts/StreamDrops.sol`](../../smart-contracts/StreamDrops.sol) | Domain separator, digest, signer validation, replay, and sale validation | +| Admin and signer manager | [`smart-contracts/StreamAdmins.sol`](../../smart-contracts/StreamAdmins.sol) | Signer lifecycle and permission target reference | +| EIP-712 tests | [`test/StreamDropsEIP712.t.sol`](../../test/StreamDropsEIP712.t.sol) | EOA, wrong domain, wrong chain, expiry, replay, cancellation, epoch, malformed, and sale-field tests | +| ERC-1271 tests | [`test/StreamDropsERC1271.t.sol`](../../test/StreamDropsERC1271.t.sol) | Contract-signer success and fail-closed tests | +| Test helper | [`test/helpers/DropAuthTestHelper.sol`](../../test/helpers/DropAuthTestHelper.sol) | Digest/signature helpers and local signer keys | +| Fixed-price EOA fixture | [`test/fixtures/drop-authorization/fixed-price-eoa.json`](../../test/fixtures/drop-authorization/fixed-price-eoa.json) | Local signed fixed-price example | +| Auction EOA fixture | [`test/fixtures/drop-authorization/auction-eoa.json`](../../test/fixtures/drop-authorization/auction-eoa.json) | Local signed auction example | +| ERC-1271 fixture | [`test/fixtures/drop-authorization/erc1271-contract-signer.json`](../../test/fixtures/drop-authorization/erc1271-contract-signer.json) | Local contract-signer example | +| Unsigned fixed-price payload | [`test/fixtures/drop-authorization/payload-generator/fixed-price-output.json`](../../test/fixtures/drop-authorization/payload-generator/fixed-price-output.json) | Deterministic unsigned typed-data output | +| Unsigned auction payload | [`test/fixtures/drop-authorization/payload-generator/auction-output.json`](../../test/fixtures/drop-authorization/payload-generator/auction-output.json) | Deterministic unsigned typed-data output | +| Payload generator | [`scripts/generate_drop_authorization_payload.py`](../../scripts/generate_drop_authorization_payload.py) | No-secret typed-data payload generator | +| Fixture checker | [`scripts/check_drop_authorization_fixtures.py`](../../scripts/check_drop_authorization_fixtures.py) | Canonical domain/type/fixture validation | +| Signing evidence checker | [`scripts/check_drop_authorization_signing_evidence.py`](../../scripts/check_drop_authorization_signing_evidence.py) | Retained evidence validation | +| Signer custody checker | [`scripts/check_signer_custody_readiness.py`](../../scripts/check_signer_custody_readiness.py) | Signer custody readiness validation | + +Raw ABIs are generated under ignored `out/` after `forge build`. Do not copy +ABI JSON by hand into frontend repositories without checking the ABI baseline, +ABI checksums, release manifest, and selected deployment address book. + +## Domain And Typed Data + +`StreamDrops` uses this EIP-712 domain: + +```text +EIP712Domain(string name,string version,uint256 chainId,address verifyingContract) +``` + +The current domain fields are: + +| Field | Required behavior | +| --- | --- | +| `name` | `6529StreamDrops` | +| `version` | `1` | +| `chainId` | The chain ID where the target `StreamDrops` contract is deployed | +| `verifyingContract` | The deployed `StreamDrops` address for the selected deployment | + +The signed primary type is: + +```text +DropAuthorization( + bytes32 dropId, + address poster, + address recipient, + address payer, + uint256 collectionId, + uint8 saleMode, + bytes32 tokenDataHash, + uint256 price, + uint256 quantity, + uint256 auctionReservePrice, + uint256 auctionEndTime, + uint256 salt, + uint256 nonce, + uint256 deadline, + uint256 signerEpoch +) +``` + +`DROP_AUTHORIZATION_TYPEHASH` covers the payload above. `DROP_ID_TYPEHASH` +covers: + +```text +DropId(address signer,uint256 signerEpoch,uint256 nonce,uint256 salt) +``` + +`dropId` must equal `deriveDropId(signer, signerEpoch, nonce, salt)`. +`tokenDataHash` must equal `keccak256(bytes(tokenData))`. App teams should +display or log hashes, not raw unreleased art payloads, unless the release or +drop process explicitly allows that payload to be public. + +EIP-712 is encoding/signing only. Replay protection requires domain +separation, storage-backed `consumedDropIds`, storage-backed +`cancelledDropIds`, signer-service-allocated `nonce` and `salt`, `deadline`, +current `signerEpoch`, signer rotation policy, and the on-chain consumed-state +write before mint or auction execution. There is no separate on-chain monotonic +nonce map; uniqueness is a signer-service obligation and is +enforced on chain through the derived `dropId` plus consumed/cancelled storage. + +## Replay And Revocation Controls + +The app must model these controls as separate state, not as one generic +"signature failed" bucket: + +| Control | Contract/source behavior | Product handling | +| --- | --- | --- | +| Domain | `domainSeparator()` includes `name`, `version`, `chainId`, and `verifyingContract` | Stop on wrong chain or wrong contract before asking for a transaction | +| `dropId` | Derived from signer, signer epoch, nonce, and salt | Treat mismatch as payload construction failure | +| `nonce` / `salt` | Signer-service allocated; no on-chain monotonic nonce map exists | Track uniqueness per signer epoch in the signing service | +| Consumed storage | `consumedDropIds[dropId]` is written before sale execution | Show replayed or already executed; re-read token/drop state | +| Cancelled storage | `cancelledDropIds[dropId]` blocks later execution | Show cancelled by operator workflow | +| Signer epoch | `signerEpoch` must equal current contract state | Request a new payload after signer rotation or compromise response | +| Deadline | `deadline >= block.timestamp` is required | Request a fresh authorization after expiry | +| Token-data hash | Exact `tokenData` bytes must hash to `tokenDataHash` | Treat mismatch as stale or tampered metadata | + +Operators should use per-drop cancellation for a single bad payload and signer +epoch rotation for signer compromise, signer migration, or broad invalidation. +If the drop surface itself is unsafe, use drop-execution pause before issuing +new signatures. + +## EOA Wallet Flow + +EOA signing is a backend/operator action in the current model. The frontend or +mobile app normally receives a signed `DropAuthorization`; it should not hold +the production signer key. + +For an EOA signer: + +1. The signing service builds or verifies typed data under the current domain. +2. The service signs the digest with the active `tdhSigner`. +3. The app submits the exact authorization, exact `tokenData`, signature bytes, + sender, and value expected by the sale mode. +4. `StreamDrops` recovers the signer from the EIP-712 digest. +5. 65-byte EOA signatures and EIP-2098 compact 64-byte signatures are + accepted. + +EOA signatures fail closed for wrong signer, wrong domain, wrong chain, +expired deadline, replayed drop, cancelled drop, stale signer epoch, bad +drop ID, token data substitution, high-s malleable signature, invalid `v`, +zero recovered signer, and malformed signature length. + +Wallet UIs should not offer "try again with edited fields." Any mutation to +`poster`, `recipient`, `payer`, `collectionId`, `saleMode`, `tokenDataHash`, +`price`, `quantity`, `auctionReservePrice`, `auctionEndTime`, `salt`, `nonce`, +`deadline`, or `signerEpoch` requires a fresh signature. + +## ERC-1271 Contract Signer Flow + +If the active `tdhSigner` is a contract, `StreamDrops` does not recover an EOA. +It calls: + +```solidity +isValidSignature(bytes32 digest, bytes signature) +``` + +The call must: + +- succeed; +- return exactly 32 bytes; and +- decode to the ERC-1271 magic value `0x1626ba7e`. + +Invalid magic, empty return, short return, extra return, reverted validation, +wrong digest, wrong signature bytes, expired authorization, and replayed +authorization fail closed with no consumed-state rollback issue. + +The ERC-1271 path uses the same EIP-712 digest and replay controls as the EOA +path. Smart wallet support is therefore not an alternative schema. It is a +different signer-validation path for the same `DropAuthorization` digest. + +## Safe Signing Flow + +Treat Safe signing as an ERC-1271 contract-signer integration until production +evidence proves a different signer model. + +A Safe-based signing setup should define: + +- the Safe address that will be the active `tdhSigner`; +- Safe network and chain ID; +- owner threshold and signer approval workflow; +- whether the Safe signs the EIP-712 typed data digest directly or validates a + pre-approved message under its own message flow; +- how the returned `signature` bytes are constructed for + `isValidSignature(bytes32,bytes)`; +- signer manager and signer epoch rotation authority; +- per-drop cancellation authority and procedure; +- retained evidence for digest, typed data, Safe transaction/message URL or + redacted reference, reviewer approval, and validation output. + +Before using Safe in public beta or production, retain signer custody +readiness evidence showing ERC-1271 status as `supported` or explicitly +documented otherwise. A frontend should show Safe validation failures as +operator/configuration failures, not as ordinary user wallet rejections. + +## WalletConnect And Mobile Handoff + +WalletConnect and mobile wallets are user-transaction channels in this repo's +current integration model. They are not the production drop-signing key path +unless a future custody decision explicitly changes that. + +For mobile and WalletConnect: + +- bind the selected chain to the signed EIP-712 domain before transaction + submission; +- show collection, sale mode, recipient, payer, price or reserve, deadline, + and signer epoch before asking the user to submit; +- preserve the exact `DropAuthorization` object and `tokenData` across deep + links and app resumes; +- re-read `isDropConsumed`, `isDropCancelled`, `signerEpoch`, pause status, + and relevant payment or auction state after reconnects; +- handle wallet rejection, session expiry, chain switch, and RPC replacement + as retryable transport states only if the signed payload is still current; + and +- never store private keys, mnemonics, production signing credentials, raw + HSM credentials, RPC secrets, or unreleased payload secrets in mobile logs, + crash reports, analytics, local storage, clipboard helpers, or support + tickets. + +If a mobile wallet requests EIP-712 signing for a user action, that should be +for the user's own wallet flow. Do not confuse user transaction approval with +the protocol's approved drop signer. + +## Backend Signing Service Boundary + +The backend signing service owns payload construction and signer coordination. +It should: + +- load the selected address book and `StreamDrops` verifying contract; +- read `tdhSigner`, `signerEpoch`, and any signer lifecycle policy; +- allocate unique signer-scoped `nonce` and `salt` values; +- compute `dropId = deriveDropId(signer, signerEpoch, nonce, salt)`; +- compute `tokenDataHash = keccak256(bytes(tokenData))`; +- enforce fixed-price and auction field contracts; +- enforce a bounded `deadline`; +- sign with the approved EOA signer or coordinate ERC-1271 contract signer + state; +- retain no-secret signing evidence, reviewer approval, digest, typed-data + hash, signature verification, signer identity, signer epoch, and custody + references; and +- return only signature bytes and public verification metadata to the app. + +Frontend, mobile, and Electron clients should: + +- request unsigned intent data or already-approved signatures from the service; +- display meaningful payload fields before submission; +- reject chain/domain mismatch before asking for a transaction; +- keep signed fields immutable after signature issuance; +- run an `eth_call` simulation with the exact sender and value before a paid + transaction when practical; and +- avoid private key handling entirely. + +## Frontend Preflight Reads + +Before enabling a mint or auction submit button, read or derive: + +| Read | Why | +| --- | --- | +| Connected wallet `chainId` | Must match the EIP-712 domain | +| Selected `StreamDrops` address | Must match `verifyingContract` | +| `domainSeparator()` | Confirms the live domain derived by the contract | +| `tdhSigner()` | Confirms expected EOA or ERC-1271 signer | +| `signerEpoch()` | Rejects stale payloads after rotation | +| `isDropConsumed(dropId)` | Rejects replayed payloads | +| `isDropCancelled(dropId)` | Rejects cancelled payloads | +| Drop execution pause state | Blocks new drop execution while paused | +| Fixed-price or auction contract state | Confirms payer/value or auction custody assumptions | + +For fixed-price payloads, additionally follow +[`docs/integrations/contract-flows.md`](contract-flows.md). For auction +payloads, additionally follow +[`docs/integrations/auction-flows.md`](auction-flows.md). + +## Failure States + +Use specific failure messages and recovery paths: + +| Failure | Likely source | Handling | +| --- | --- | --- | +| Wrong domain | Wrong `name`, `version`, `chainId`, or `verifyingContract` | Stop and regenerate or switch network | +| Wrong signer | EOA recovery or ERC-1271 signer does not match `tdhSigner` | Request operator review and new payload | +| Expired deadline | `deadline` is stale | Request fresh authorization | +| Replayed payload | `consumedDropIds[dropId]` is true | Show already executed and re-read token/drop state | +| Cancelled drop | `cancelledDropIds[dropId]` is true | Show cancelled and stop | +| Stale epoch | Payload `signerEpoch` differs from contract state | Request fresh authorization | +| Malleable signature | EOA high-s or invalid `v` | Reject and alert signing service | +| Invalid signature length | Not 65-byte EOA or EIP-2098 64-byte compact signature | Reject before submission if possible | +| Zero recovered signer | ECDSA recovery returns zero | Reject and alert signing service | +| Unsupported contract signature | ERC-1271 call reverts or returns bad length/magic | Treat as signer-configuration failure | +| Wrong digest | ERC-1271 signer validates a different digest | Request signer operator review | +| Zero-address signer | `tdhSigner` should never be an unconfigured zero signer | Treat as deployment/configuration failure | +| Zero-address recipient | Invalid for fixed-price; required for auction | Apply sale-mode-specific handling | +| Non-zero auction recipient | Invalid auction payload | Request fresh authorization | +| Free fixed-price payer mismatch | `payer` must be zero when `price == 0` | Request fresh authorization | +| Paid payer mismatch | `payer` must equal `msg.sender` when `price > 0` | Ask correct payer wallet or request fresh authorization | +| Value mismatch | `msg.value` does not equal signed `price` or zero auction value | Rebuild transaction with exact value | +| Token data substitution | `tokenDataHash` mismatch | Treat as stale or tampered payload | +| Wallet rejection | User rejects transaction | Retry only if payload is still current | +| WalletConnect session expired | Transport disconnect | Reconnect and re-run preflight | + +Do not collapse these into "signature failed." The recovery path is different +for user rejection, wrong network, stale epoch, signer compromise, cancelled +drop, and contract-signer misconfiguration. + +## Security And UX Requirements + +Minimum app requirements: + +- show the target network, contract, collection, sale mode, recipient or auction + zero-recipient rule, payer, price or reserve, deadline, and signer epoch; +- keep the typed-data payload immutable after signing; +- hash and compare token data before submission; +- simulate `mintDrop` with the exact sender and value where practical; +- re-read consumed/cancelled/epoch state after wallet reconnect or pending + transaction replacement; +- never auto-retry with mutated fields; +- never ask a user or operator to paste a private key or mnemonic; +- document the policy as no private keys in client, support, analytics, logs, + fixtures, or release evidence; +- keep production signer material out of browser, mobile, Electron renderer, + logs, analytics, crash reports, and committed fixtures; +- mark Safe/ERC-1271 validation failures as operator-signing failures; and +- link to signer custody and incident response when signer compromise is + suspected. + +Electron apps should isolate renderer processes from signing-service secrets. +If an Electron shell is used, it should treat release artifacts, typed data, and +signatures as untrusted external input until validated, and should not expose +backend signing credentials to renderer JavaScript. + +## Validation Commands + +Run these when editing this guide: + +```sh +python scripts/test_wallet_signature_flows.py +python scripts/check_wallet_signature_flows.py +python scripts/test_integrations_readme.py +python scripts/check_integrations_readme.py +python scripts/test_release_readiness.py +python scripts/check_release_readiness.py +python scripts/test_drop_authorization_fixtures.py +python scripts/check_drop_authorization_fixtures.py +python scripts/test_drop_authorization_signing_evidence.py +python scripts/check_drop_authorization_signing_evidence.py +python scripts/test_signer_custody_readiness.py +python scripts/check_signer_custody_readiness.py +python scripts/check_changelog.py +python scripts/generate_release_manifest.py --check +python scripts/generate_bytecode_release_proof.py --check +python scripts/generate_release_checksums.py --check +forge test --match-path test/StreamDropsEIP712.t.sol +forge test --match-path test/StreamDropsERC1271.t.sol +``` + +If release-manifest-tracked docs or scripts changed, regenerate and check the +release manifest, bytecode proof, and checksum bundle. + +## Maintenance + +Update this guide when any of these change: + +- `StreamDrops.DropAuthorization` fields; +- `EIP712_NAME`, `EIP712_VERSION`, `EIP712_DOMAIN_TYPEHASH`, + `DROP_AUTHORIZATION_TYPEHASH`, or `DROP_ID_TYPEHASH`; +- `domainSeparator()`, `deriveDropId()`, or `hashDropAuthorization()`; +- EOA recovery rules, EIP-2098 support, malleability checks, or zero signer + handling; +- ERC-1271 `isValidSignature` behavior; +- `tdhSigner`, `signerEpoch`, `consumedDropIds`, or `cancelledDropIds` + semantics; +- signer custody readiness evidence; +- Safe signing policy; +- WalletConnect, mobile, or Electron guidance; or +- fixed-price or auction sale-field contracts. diff --git a/docs/release-readiness.md b/docs/release-readiness.md index a9920773..add8b1d3 100644 --- a/docs/release-readiness.md +++ b/docs/release-readiness.md @@ -36,6 +36,10 @@ Use [`docs/integrations/auction-flows.md`](integrations/auction-flows.md) as the auction frontend and indexer flow spec for current INT-003 integration work. Use +[`docs/integrations/wallets-and-signatures.md`](integrations/wallets-and-signatures.md) +as the wallet, EIP-712, ERC-1271, and Safe signing guide for current INT-004 +integration work. +Use [`release-artifacts/latest/public-beta-blockers.md`](../release-artifacts/latest/public-beta-blockers.md) and [`release-artifacts/latest/production-release-blockers.md`](../release-artifacts/latest/production-release-blockers.md) @@ -312,6 +316,7 @@ Audit and protocol evidence: - [docs/integrations/README.md](integrations/README.md) - [docs/integrations/contract-flows.md](integrations/contract-flows.md) - [docs/integrations/auction-flows.md](integrations/auction-flows.md) +- [docs/integrations/wallets-and-signatures.md](integrations/wallets-and-signatures.md) Release artifacts: @@ -371,6 +376,8 @@ python scripts/test_contract_flows.py python scripts/check_contract_flows.py python scripts/test_auction_flows.py python scripts/check_auction_flows.py +python scripts/test_wallet_signature_flows.py +python scripts/check_wallet_signature_flows.py python scripts/test_drop_authorization_payload_generator.py python scripts/generate_drop_authorization_payload.py --input test/fixtures/drop-authorization/payload-generator/fixed-price-input.json --output test/fixtures/drop-authorization/payload-generator/fixed-price-output.json --check python scripts/generate_drop_authorization_payload.py --input test/fixtures/drop-authorization/payload-generator/auction-input.json --output test/fixtures/drop-authorization/payload-generator/auction-output.json --check diff --git a/docs/signer-custody-readiness.md b/docs/signer-custody-readiness.md index dcb6a458..e07051a4 100644 --- a/docs/signer-custody-readiness.md +++ b/docs/signer-custody-readiness.md @@ -8,6 +8,7 @@ public-beta readiness, or live signer-service integration. Use this guide with [`docs/drop-authorization-signing.md`](drop-authorization-signing.md), +[`docs/integrations/wallets-and-signatures.md`](integrations/wallets-and-signatures.md), [`docs/deployment.md#admin-ceremony-evidence`](deployment.md#admin-ceremony-evidence), [`docs/incident-response.md`](incident-response.md), [`docs/non-local-release-evidence.md`](non-local-release-evidence.md), and diff --git a/ops/AUTONOMOUS_RUN.md b/ops/AUTONOMOUS_RUN.md index 22a188ff..db5916b1 100644 --- a/ops/AUTONOMOUS_RUN.md +++ b/ops/AUTONOMOUS_RUN.md @@ -32,15 +32,15 @@ tests, security hardening, deployment discipline, and release/audit readiness. | Field | Value | | --- | --- | | Remote | `https://github.com/6529-Collections/6529Stream.git` | -| Active PR branch | `codex/auction-flow-spec` | -| Last merged PR | `https://github.com/6529-Collections/6529Stream/pull/393` | -| Active issue | `https://github.com/6529-Collections/6529Stream/issues/394` | -| Active PR | `https://github.com/6529-Collections/6529Stream/pull/395` | -| Next issue | TBD after INT-003; likely `INT-004` unless bot feedback, CI, or a higher-priority release-evidence blocker changes the queue. `https://github.com/6529-Collections/6529Stream/issues/217` (`testnet_deployment_rehearsal`) remains open for real reviewed testnet evidence, but Sepolia execution is blocked locally by missing RPC/signer/funding environment | +| Active PR branch | `codex/wallet-signing-guide` | +| Last merged PR | `https://github.com/6529-Collections/6529Stream/pull/395` | +| Active issue | `https://github.com/6529-Collections/6529Stream/issues/396` | +| Active PR | `https://github.com/6529-Collections/6529Stream/pull/397` | +| Next issue | TBD after INT-004. `https://github.com/6529-Collections/6529Stream/issues/217` (`testnet_deployment_rehearsal`) remains open for real reviewed testnet evidence, but Sepolia execution is blocked locally by missing RPC/signer/funding environment | | Roadmap file | `ops/ROADMAP.md` | | Execution backlog file | `ops/EXECUTION_BACKLOG.md` | | State file | `ops/AUTONOMOUS_RUN.md` | -| Last updated | `2026-06-15 10:52 UTC` | +| Last updated | `2026-06-15 11:23 UTC` | ## Packaging Notes @@ -15188,6 +15188,10 @@ Outcome: | 2026-06-13 13:38 | Merge PR #236 | Release evidence issue body sync merged as `1a825466d2333dc75e2fb8e2aeb11dc9b0dccc5a` after final CI run `27468161616`, CodeRabbit success, resolved review threads, and issue #235 closure | | 2026-06-13 13:41 | Start Queue Item 113 | Issue #237 opened to reconcile PR #236 merge evidence and roadmap metadata; tracker issues #215 through #231 had `release` and `roadmap` labels re-applied to match committed `applied_labels` | | 2026-06-13 13:44 | Open PR #238 | Release evidence body-sync state reconciliation PR opened on head `5d6002d78b75da03ae3ce45dbcfefecdcd4fa8b8`; CodeRabbit review will be requested after this PR-state follow-up commit | +| 2026-06-15 11:01 | Merge PR #395 | Auction integration flow spec merged as `604f0db28dffb89c24c24bf0d28b5d028987cad0` after CI run #838 passed, 6529bot reported no new findings on final head `8c01b53f0595b360269c4d6c2b8e26987914c289`, review threads were empty, and CodeRabbit status was success despite an informational rate-limit comment. | +| 2026-06-15 11:02 | Create issue #396 and select INT-004 | Next substantive integration-readiness slice is the wallet, EIP-712, ERC-1271, and Safe signing guide on branch `codex/wallet-signing-guide`; no existing open issue matched, so issue #396 now tracks the guide/checker/local-CI/release-manifest work. | +| 2026-06-15 11:20 | Open PR #397 for INT-004 | PR #397 is open on branch `codex/wallet-signing-guide`, closes issue #396, and CodeRabbit review was requested. Local evidence includes the wallet guide checker/test, integration/release-readiness checks, release manifest/proof/checksum checks, focused EIP-712/ERC-1271 Forge tests, PowerShell parser check, and `git diff --check`. | +| 2026-06-15 11:23 | Address PR #397 bot nice-to-have | Tightened `scripts/check_wallet_signature_flows.py` required phrases from generic schema words to code-token field names so the checker better protects the typed-data domain and replay fields. | ## Resume Instructions diff --git a/ops/EXECUTION_BACKLOG.md b/ops/EXECUTION_BACKLOG.md index e94438ab..d361a0f0 100644 --- a/ops/EXECUTION_BACKLOG.md +++ b/ops/EXECUTION_BACKLOG.md @@ -1903,7 +1903,7 @@ Dependencies: `INT-001`. ### INT-003: Add Auction Frontend And Indexer Flow Spec -Status: PR #395 draft open on issue #394 / branch `codex/auction-flow-spec`. +Status: Merged in PR #395; issue #394 closed completed. Gate: G/D. @@ -2020,46 +2020,108 @@ Dependencies: `INT-001`. ### INT-004: Add Wallet, EIP-712, ERC-1271, And Safe Signing Guide -Status: Planned. +Status: PR #397 open on issue #396 / branch `codex/wallet-signing-guide`. Gate: G/F. Problem: Signatures are a core integration and security surface. Product teams -need wallet-specific guidance that avoids replay, wrong-domain, and smart-wallet +need wallet-specific guidance that avoids replay, wrong-domain, stale-epoch, +cancelled-drop, Safe/ERC-1271, WalletConnect, and backend signer custody mistakes. Outcome: A dedicated wallet/signature integration guide covers browser wallets, -WalletConnect, Safe/ERC-1271, backend signers, signer epoch, cancellation, and -error handling. +WalletConnect, mobile handoff, Electron boundaries, Safe/ERC-1271, backend +signers, signer epoch, cancellation, replay controls, frontend preflight reads, +and error handling. The guide is checked, release-tracked, and explicitly +pre-audit / not production-ready. Files likely touched: - `docs/integrations/wallets-and-signatures.md` +- `docs/integrations/README.md` +- `docs/release-readiness.md` +- `release-artifacts/README.md` - `docs/drop-authorization-signing.md` - `docs/signer-custody-readiness.md` +- `scripts/check_wallet_signature_flows.py` +- `scripts/test_wallet_signature_flows.py` +- `scripts/check_integrations_readme.py` +- `scripts/test_integrations_readme.py` +- `scripts/check_release_readiness.py` +- `scripts/test_release_readiness.py` +- `scripts/generate_release_manifest.py` +- Makefile, Bash, PowerShell, and CI gate wiring +- generated release manifest, bytecode proof, and checksum artifacts if docs + or manifest inputs change Implementation steps: 1. Document domain fields: name, version, chain ID, verifying contract. 2. Document nonce/drop ID, deadline, signer epoch, consumed storage, and - cancellation. -3. Document EOA flow and ERC-1271 flow. -4. Document WalletConnect/mobile signing handoff caveats. -5. Document Safe signing and validation expectations. -6. Document common frontend error states and recovery. + cancellation, while making clear there is no separate on-chain monotonic + nonce map. +3. Document EOA flow, including 65-byte and EIP-2098 compact signatures, + low-s, invalid-v, zero-signer, and malformed-length failure handling. +4. Document ERC-1271 flow, including exact `isValidSignature(bytes32,bytes)` + digest/signature behavior, 32-byte return length, magic value `0x1626ba7e`, + invalid magic, revert, empty/short/extra return, wrong digest, and wrong + signature bytes. +5. Document Safe signing and validation expectations as a contract-signer + integration, without claiming reviewed Safe custody exists. +6. Document WalletConnect/mobile handoff caveats and Electron secret-boundary + expectations. +7. Document backend signing-service responsibilities and frontend preflight + reads. +8. Document common frontend error states and recovery. +9. Add a checker and tests requiring headings, maturity phrases, flow-critical + terms, local source links, and validation commands. +10. Wire the checker into local and CI gates. +11. Link the guide from integration, release-readiness, release-artifact, + changelog, backlog, and autonomous-run docs. +12. Regenerate downstream release artifacts after docs/checker changes. Required tests/checks: -- Drop authorization fixture checks if examples are added. -- Markdown heading check. +- `python scripts/test_wallet_signature_flows.py` +- `python scripts/check_wallet_signature_flows.py` +- `python scripts/test_integrations_readme.py` +- `python scripts/check_integrations_readme.py` +- `python scripts/test_release_readiness.py` +- `python scripts/check_release_readiness.py` +- `python scripts/test_drop_authorization_fixtures.py` +- `python scripts/check_drop_authorization_fixtures.py` +- `python scripts/test_drop_authorization_signing_evidence.py` +- `python scripts/check_drop_authorization_signing_evidence.py` +- `python scripts/test_signer_custody_readiness.py` +- `python scripts/check_signer_custody_readiness.py` +- `python scripts/test_release_manifest.py` +- `python scripts/generate_release_manifest.py --check` +- `python scripts/test_bytecode_release_proof.py` +- `python scripts/generate_bytecode_release_proof.py --check` +- `python scripts/test_release_checksums.py` +- `python scripts/generate_release_checksums.py --check` +- `python scripts/check_changelog.py` +- `forge test --match-path test/StreamDropsEIP712.t.sol` +- `forge test --match-path test/StreamDropsERC1271.t.sol` - `git diff --check`. Acceptance criteria: - The guide makes clear that EIP-712 is encoding/signing, not replay protection by itself. -- Smart wallet support is documented with contract-signature behavior. -- Wrong-domain and stale-epoch failure modes are user-visible. +- The guide states replay depends on domain separation, consumed/cancelled + storage, signer-service nonce/salt allocation, deadline, signer epoch, + signer rotation, and consumed-state writes. +- The guide documents that there is no on-chain monotonic nonce map. +- Smart wallet support is documented with ERC-1271 contract-signature + behavior, and Safe support is scoped to that behavior unless future reviewed + custody evidence says otherwise. +- Wrong signer, wrong domain, wrong chain, expired, replayed, cancelled, stale + epoch, malleable, invalid length, zero signer, wrong digest, wrong signature + bytes, zero recipient, non-zero auction recipient, token-data substitution, + and value/payer mismatches are user-visible. +- Local/CI gates fail if the guide drops required maturity language, headings, + source links, validation commands, or signature-critical terms. Evidence artifacts: None. diff --git a/release-artifacts/README.md b/release-artifacts/README.md index 9a87740e..1d013edb 100644 --- a/release-artifacts/README.md +++ b/release-artifacts/README.md @@ -45,6 +45,7 @@ python scripts/check_audit_package.py python scripts/check_integrations_readme.py python scripts/check_contract_flows.py python scripts/check_auction_flows.py +python scripts/check_wallet_signature_flows.py python scripts/check_release_readiness.py python scripts/generate_release_manifest.py python scripts/generate_bytecode_release_proof.py @@ -122,6 +123,8 @@ python scripts/test_contract_flows.py python scripts/check_contract_flows.py python scripts/test_auction_flows.py python scripts/check_auction_flows.py +python scripts/test_wallet_signature_flows.py +python scripts/check_wallet_signature_flows.py python scripts/test_release_readiness.py python scripts/check_release_readiness.py python scripts/test_release_manifest.py diff --git a/release-artifacts/latest/SHA256SUMS b/release-artifacts/latest/SHA256SUMS index 82242a87..768ddb5f 100644 --- a/release-artifacts/latest/SHA256SUMS +++ b/release-artifacts/latest/SHA256SUMS @@ -74,7 +74,7 @@ b5efd4cbd2ad950f2f8d2c6e756bf0877a55b27254b278a049d75b2f032504d8 release-artifa 5a41934f1e1346b006df2434adfb1edb8c56a58a16c4d6053a2001af438bb7e9 release-artifacts/evidence/release-evidence-live-audit-report-template.md d33e7d8d32707c6ea91f8402080eda0e650a915b7987eb433ba1e4fcf09a4825 release-artifacts/evidence/testnet-deployment-rehearsal/testnet-deployment-rehearsal-retained-artifact-template.md 6678bda73ee972cb1ae69f44bcea0a0bc2da8eaaaa6c93651ba8e0cf42fcce48 release-artifacts/latest/abi-checksums.json -dca293bf7b4d7b1118b61a144d0a02c82ded62fc9136a04bbf8bf1fd00bc26c5 release-artifacts/latest/bytecode-release-proof.json +b5db1a6a52493f7df4f30133fe3518ec4e446dbeb88c4f70eb980683fabd35c4 release-artifacts/latest/bytecode-release-proof.json 58f4bce9386d87ee9d59a9bc91184db2e04f3efeb366b280a9f94506bc02f1ce release-artifacts/latest/dependency-artifact-manifest.json c6407a256a29b6a3b4606a8e2f7cce2de1a286f287702bf9f2cd5471f83ea7eb release-artifacts/latest/event-topic-catalog.json 8a8a41d89757d3d16f0718638d1175290dcd00cfea86204665cd525f608d7c1a release-artifacts/latest/interface-ids.json @@ -91,8 +91,8 @@ cf993778a1c339fef506c16da7887cb9612c702ec95608ae9cd49a9958077d93 release-artifa 8e108e6eff609c72e7c3547e01bd72d09cdc092d6c2aef8a34744828061337f9 release-artifacts/latest/release-evidence-live-audit-report-archive.md 9f011c676ce314f730e6ea7bbeb57651d52fc970dd9aafb0481511fe3028cbe7 release-artifacts/latest/release-evidence-packet-index.json dadca5b8b926a1f6321c5755619ace2b7f2346ab7e8aad878f34fdc9a44c8c76 release-artifacts/latest/release-evidence-packet-index.md -86c873a653ce6e69883d010bc58dd956a0681c817e52c2c708a92d10893b6c2f release-artifacts/latest/release-manifest.json -339b001ab98757b2fadb1f3e5d27d22adccf837310579b3424a64732333bc6fd release-artifacts/latest/risk-register.json +249d2a073819d3d2ceb42bcb60206d6a79c335b5bcefe715e9f2441efce9046b release-artifacts/latest/release-manifest.json +91c82e4ab7777f43861356ee8490d19d18337bd0cf20c726a494a329c66a864f release-artifacts/latest/risk-register.json 1fbea177413542f60749db26e5e150a7f7126bea5679eacf994c7982513fe983 release-artifacts/latest/source-verification-inputs.json 60e0efe388b974d81bfad266a1c9fb10d55c3104ae24ba268e6e68441edc4370 release-artifacts/schema/drop-authorization-signing-evidence.schema.json 5a5aede0ab1b7bee194e495bdac98e0f37b814879be62a72ce819be07da82c1a release-artifacts/schema/non-local-release-evidence.schema.json @@ -103,7 +103,7 @@ c1484a62332afd3e6d3b0a7b3eb14fd90473da207516115d4504ab04ade60af3 release-artifa f1e1d812c2e589762c2b3676a89d3bf1ddb74dfdf7dbfa403444b465eff34718 release-artifacts/schema/signer-custody-readiness.schema.json 0a8e8632f12613fdbe980297ea39cd3e2b2365044c08ed542745df8cc626e6d8 release-artifacts/signatures/anvil-6529stream-v0.1.0-001-local.json f2d4e88b27a0365314e1c346fb5f824dcd364328e9d4cefc57f477b59233fc80 release-artifacts/signer-custody-readiness/signer-custody-readiness-retained-artifact.txt -8dc2b53591207d713616c9acdb3d0841aa3bee47594b82a6894aec83264460ff release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json +b735c3468d421afe7e80ad71d52673f2e2c6ba9f863be8cc9ebcb120a2f15cb1 release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json 1bdf6b0bf350944b07b86c642a10821d57e8a5d14d31d31d1f1081681c7268c9 test/fixtures/drop-authorization/auction-eoa.json e687608f6421f1efb34be9e6a11a863ebcfbc6c7f84f6c8fd62dafe1bbe8c4c7 test/fixtures/drop-authorization/erc1271-contract-signer.json 1ad9336461c4cfbdd000eb40f8fa7066012d6b46cdd410106fd65cef1261b40e test/fixtures/drop-authorization/fixed-price-eoa.json diff --git a/release-artifacts/latest/bytecode-release-proof.json b/release-artifacts/latest/bytecode-release-proof.json index c7a43b09..7ee3d76c 100644 --- a/release-artifacts/latest/bytecode-release-proof.json +++ b/release-artifacts/latest/bytecode-release-proof.json @@ -4,8 +4,8 @@ "source": { "release_manifest": { "path": "release-artifacts/latest/release-manifest.json", - "sha256": "sha256:86c873a653ce6e69883d010bc58dd956a0681c817e52c2c708a92d10893b6c2f", - "size_bytes": 94862 + "sha256": "sha256:249d2a073819d3d2ceb42bcb60206d6a79c335b5bcefe715e9f2441efce9046b", + "size_bytes": 95063 }, "abi_checksums": { "path": "release-artifacts/latest/abi-checksums.json", diff --git a/release-artifacts/latest/release-checksums.json b/release-artifacts/latest/release-checksums.json index 51af03a7..2976271b 100644 --- a/release-artifacts/latest/release-checksums.json +++ b/release-artifacts/latest/release-checksums.json @@ -28,7 +28,7 @@ "text_checksum_file": { "path": "release-artifacts/latest/SHA256SUMS", "format": "sha256sum", - "sha256": "sha256:64ccaf559e155fcf4044b2f86bd287733f4cb9de801a9359cabde31d588db8d4" + "sha256": "sha256:d0e7c35b65065d82e376157064f3963b498808627221257c194a87f84783fb0e" }, "manifest_file": { "path": "release-artifacts/latest/release-checksums.json", @@ -417,7 +417,7 @@ }, { "path": "release-artifacts/latest/bytecode-release-proof.json", - "sha256": "sha256:dca293bf7b4d7b1118b61a144d0a02c82ded62fc9136a04bbf8bf1fd00bc26c5", + "sha256": "sha256:b5db1a6a52493f7df4f30133fe3518ec4e446dbeb88c4f70eb980683fabd35c4", "size_bytes": 67013 }, { @@ -502,12 +502,12 @@ }, { "path": "release-artifacts/latest/release-manifest.json", - "sha256": "sha256:86c873a653ce6e69883d010bc58dd956a0681c817e52c2c708a92d10893b6c2f", - "size_bytes": 94862 + "sha256": "sha256:249d2a073819d3d2ceb42bcb60206d6a79c335b5bcefe715e9f2441efce9046b", + "size_bytes": 95063 }, { "path": "release-artifacts/latest/risk-register.json", - "sha256": "sha256:339b001ab98757b2fadb1f3e5d27d22adccf837310579b3424a64732333bc6fd", + "sha256": "sha256:91c82e4ab7777f43861356ee8490d19d18337bd0cf20c726a494a329c66a864f", "size_bytes": 20415 }, { @@ -562,7 +562,7 @@ }, { "path": "release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json", - "sha256": "sha256:8dc2b53591207d713616c9acdb3d0841aa3bee47594b82a6894aec83264460ff", + "sha256": "sha256:b735c3468d421afe7e80ad71d52673f2e2c6ba9f863be8cc9ebcb120a2f15cb1", "size_bytes": 3470 }, { diff --git a/release-artifacts/latest/release-manifest.json b/release-artifacts/latest/release-manifest.json index dca2e3a3..567e3348 100644 --- a/release-artifacts/latest/release-manifest.json +++ b/release-artifacts/latest/release-manifest.json @@ -104,7 +104,7 @@ }, "risk_register": { "path": "release-artifacts/latest/risk-register.json", - "sha256": "sha256:339b001ab98757b2fadb1f3e5d27d22adccf837310579b3424a64732333bc6fd", + "sha256": "sha256:91c82e4ab7777f43861356ee8490d19d18337bd0cf20c726a494a329c66a864f", "size_bytes": 20415, "schema_version": "6529stream.risk-register.v1", "maturity": "pre_audit_local_baseline", @@ -1346,7 +1346,7 @@ "signer_custody_readiness": [ { "path": "release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json", - "sha256": "sha256:8dc2b53591207d713616c9acdb3d0841aa3bee47594b82a6894aec83264460ff", + "sha256": "sha256:b735c3468d421afe7e80ad71d52673f2e2c6ba9f863be8cc9ebcb120a2f15cb1", "size_bytes": 3470, "schema_version": "6529stream.signer-custody-readiness.v1", "evidence_id": "signer-custody-readiness-template", @@ -1435,7 +1435,7 @@ "monitoring_status": "not_available_local", "runbook": { "path": "docs/signer-custody-readiness.md", - "sha256": "sha256:6d681967068e3c1dddbd3f59f46bd0ede5435f0ee9dffdb07f95283b5cc85146" + "sha256": "sha256:b8f6b6e01ccf445f52e4a95e09188bf79ed8b84000c4e796c7583012ced685b3" }, "alerting_reference": "TBD", "incident_response_runbook": { @@ -1804,8 +1804,8 @@ "release_notes_and_policy": { "changelog": { "path": "CHANGELOG.md", - "sha256": "sha256:cea60315eb09e646d1296cc84bb538500bdedf545f1a375c1ed29cf81eaac3c4", - "size_bytes": 29487 + "sha256": "sha256:3414fc4ab635fb915126a088137a88858e6ae41b4c5cf0c90dadf005980b8c55", + "size_bytes": 29998 }, "governance_docs": [ { @@ -1865,23 +1865,23 @@ }, { "path": "docs/drop-authorization-signing.md", - "sha256": "sha256:39ca3ad966aabd82a8029ce700ca0b239e6788a6516b9421c85ed0db3574c1e7", - "size_bytes": 15182 + "sha256": "sha256:c6b1f8b2cb3cf0007fc2cbd1b91e4db3e9bcdd439b24b600b3d54c19b0998b85", + "size_bytes": 15454 }, { "path": "docs/signer-custody-readiness.md", - "sha256": "sha256:6d681967068e3c1dddbd3f59f46bd0ede5435f0ee9dffdb07f95283b5cc85146", - "size_bytes": 4789 + "sha256": "sha256:b8f6b6e01ccf445f52e4a95e09188bf79ed8b84000c4e796c7583012ced685b3", + "size_bytes": 4878 }, { "path": "docs/release-readiness.md", - "sha256": "sha256:f138b534dfe44f287f5ac497889f88c2087ec44528a7b3bc90d4798df5eb58d4", - "size_bytes": 34521 + "sha256": "sha256:fb1e9539848649336c93fcd625f31015cf619e3c9e1758b0f60b31faf5176a72", + "size_bytes": 34889 }, { "path": "docs/integrations/README.md", - "sha256": "sha256:a084321f6bbfea343ea1ce74067c11ba4a9cf0b5451f051a3d9539356ae54d2a", - "size_bytes": 14578 + "sha256": "sha256:828e80c2430562f14beadfc20eeebe204f1598779870b5952d673b5206f6584e", + "size_bytes": 15360 }, { "path": "docs/integrations/contract-flows.md", @@ -1893,6 +1893,11 @@ "sha256": "sha256:ba531a197cc7c8f5732732911ec3ce0d3a819633276cfe81751319308d266780", "size_bytes": 30766 }, + { + "path": "docs/integrations/wallets-and-signatures.md", + "sha256": "sha256:924fa149b1dd0ac87f5c52e7e73a13cc413aa09cb23c459ff37d5af1336455d1", + "size_bytes": 23179 + }, { "path": "docs/tooling.md", "sha256": "sha256:92c00d2d5bb78b4c3ca769da162f6bc414d9d8566407969e2342c5a336dc0867", diff --git a/release-artifacts/latest/risk-register.json b/release-artifacts/latest/risk-register.json index 58957ab0..aa78fa7d 100644 --- a/release-artifacts/latest/risk-register.json +++ b/release-artifacts/latest/risk-register.json @@ -14,7 +14,7 @@ }, { "path": "ops/EXECUTION_BACKLOG.md", - "sha256": "sha256:6952d4b4bc34d36206e1c721fd7ae4bf2466ed92523d2843e269ce3b13739838" + "sha256": "sha256:233207388ffe48f3fb0e3d1212dd27a5bcd22b697daf848d0978dad5d5f26ba2" }, { "path": "docs/audit-package.md", @@ -22,7 +22,7 @@ }, { "path": "docs/release-readiness.md", - "sha256": "sha256:f138b534dfe44f287f5ac497889f88c2087ec44528a7b3bc90d4798df5eb58d4" + "sha256": "sha256:fb1e9539848649336c93fcd625f31015cf619e3c9e1758b0f60b31faf5176a72" }, { "path": "docs/known-blockers.md", @@ -126,7 +126,7 @@ }, { "path": "ops/EXECUTION_BACKLOG.md", - "sha256": "sha256:6952d4b4bc34d36206e1c721fd7ae4bf2466ed92523d2843e269ce3b13739838" + "sha256": "sha256:233207388ffe48f3fb0e3d1212dd27a5bcd22b697daf848d0978dad5d5f26ba2" } ], "risk_acceptance": null @@ -155,7 +155,7 @@ "evidence": [ { "path": "docs/release-readiness.md", - "sha256": "sha256:f138b534dfe44f287f5ac497889f88c2087ec44528a7b3bc90d4798df5eb58d4" + "sha256": "sha256:fb1e9539848649336c93fcd625f31015cf619e3c9e1758b0f60b31faf5176a72" }, { "path": "release-artifacts/latest/public-beta-evidence.json", @@ -191,7 +191,7 @@ "evidence": [ { "path": "docs/signer-custody-readiness.md", - "sha256": "sha256:6d681967068e3c1dddbd3f59f46bd0ede5435f0ee9dffdb07f95283b5cc85146" + "sha256": "sha256:b8f6b6e01ccf445f52e4a95e09188bf79ed8b84000c4e796c7583012ced685b3" }, { "path": "deployments/admin-ceremony/admin-ceremony-evidence-template.json", @@ -231,7 +231,7 @@ }, { "path": "docs/release-readiness.md", - "sha256": "sha256:f138b534dfe44f287f5ac497889f88c2087ec44528a7b3bc90d4798df5eb58d4" + "sha256": "sha256:fb1e9539848649336c93fcd625f31015cf619e3c9e1758b0f60b31faf5176a72" }, { "path": "release-artifacts/latest/public-beta-blockers.md", @@ -266,7 +266,7 @@ }, { "path": "ops/EXECUTION_BACKLOG.md", - "sha256": "sha256:6952d4b4bc34d36206e1c721fd7ae4bf2466ed92523d2843e269ce3b13739838" + "sha256": "sha256:233207388ffe48f3fb0e3d1212dd27a5bcd22b697daf848d0978dad5d5f26ba2" }, { "path": "docs/metadata.md", @@ -440,7 +440,7 @@ }, { "path": "ops/EXECUTION_BACKLOG.md", - "sha256": "sha256:6952d4b4bc34d36206e1c721fd7ae4bf2466ed92523d2843e269ce3b13739838" + "sha256": "sha256:233207388ffe48f3fb0e3d1212dd27a5bcd22b697daf848d0978dad5d5f26ba2" }, { "path": "docs/tooling.md", diff --git a/release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json b/release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json index c4322488..70f1d12f 100644 --- a/release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json +++ b/release-artifacts/signer-custody-readiness/signer-custody-readiness-template.json @@ -46,7 +46,7 @@ "monitoring_status": "not_available_local", "runbook": { "path": "docs/signer-custody-readiness.md", - "sha256": "sha256:6d681967068e3c1dddbd3f59f46bd0ede5435f0ee9dffdb07f95283b5cc85146" + "sha256": "sha256:b8f6b6e01ccf445f52e4a95e09188bf79ed8b84000c4e796c7583012ced685b3" }, "alerting_reference": "TBD", "incident_response_runbook": { diff --git a/scripts/check.ps1 b/scripts/check.ps1 index 5c34e727..221c246a 100644 --- a/scripts/check.ps1 +++ b/scripts/check.ps1 @@ -170,6 +170,8 @@ forge build --sizes --via-ir --skip test --skip script --force & $pythonPath @pythonArgs "scripts\check_contract_flows.py" & $pythonPath @pythonArgs "scripts\test_auction_flows.py" & $pythonPath @pythonArgs "scripts\check_auction_flows.py" +& $pythonPath @pythonArgs "scripts\test_wallet_signature_flows.py" +& $pythonPath @pythonArgs "scripts\check_wallet_signature_flows.py" & $pythonPath @pythonArgs "scripts\test_release_readiness.py" & $pythonPath @pythonArgs "scripts\check_release_readiness.py" & $pythonPath @pythonArgs "scripts\test_release_manifest.py" diff --git a/scripts/check.sh b/scripts/check.sh index f1aaae14..611390c5 100644 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -125,6 +125,8 @@ forge build --sizes --via-ir --skip test --skip script --force "$python_bin" scripts/check_contract_flows.py "$python_bin" scripts/test_auction_flows.py "$python_bin" scripts/check_auction_flows.py +"$python_bin" scripts/test_wallet_signature_flows.py +"$python_bin" scripts/check_wallet_signature_flows.py "$python_bin" scripts/test_release_readiness.py "$python_bin" scripts/check_release_readiness.py "$python_bin" scripts/test_release_manifest.py diff --git a/scripts/check_integrations_readme.py b/scripts/check_integrations_readme.py index 80f483c6..cf932f4f 100644 --- a/scripts/check_integrations_readme.py +++ b/scripts/check_integrations_readme.py @@ -54,6 +54,7 @@ "INT-003", "auction frontend and indexer flow spec", "INT-004", + "wallet, EIP-712, ERC-1271, and Safe signing guide", "INT-005", "INT-006", "INT-007", @@ -66,6 +67,8 @@ "python scripts/check_integrations_readme.py", "python scripts/test_auction_flows.py", "python scripts/check_auction_flows.py", + "python scripts/test_wallet_signature_flows.py", + "python scripts/check_wallet_signature_flows.py", "python scripts/check_release_readiness.py", "python scripts/check_changelog.py", ] @@ -85,6 +88,7 @@ "docs/known-blockers.md", "docs/integrations/contract-flows.md", "docs/integrations/auction-flows.md", + "docs/integrations/wallets-and-signatures.md", "release-artifacts/README.md", "release-artifacts/contracts.json", "release-artifacts/baselines/v0.1.0/abi-surface.json", diff --git a/scripts/check_release_readiness.py b/scripts/check_release_readiness.py index a6243eda..aee5a770 100644 --- a/scripts/check_release_readiness.py +++ b/scripts/check_release_readiness.py @@ -74,6 +74,7 @@ "integration entrypoint", "fixed-price mint and drop authorization flow spec", "auction frontend and indexer flow spec", + "wallet, EIP-712, ERC-1271, and Safe signing guide", "drop authorization signing fixtures", "unsigned payload-generator examples", "drop authorization signing evidence", @@ -94,6 +95,8 @@ "python scripts/check_contract_flows.py", "python scripts/test_auction_flows.py", "python scripts/check_auction_flows.py", + "python scripts/test_wallet_signature_flows.py", + "python scripts/check_wallet_signature_flows.py", "python scripts/test_drop_authorization_payload_generator.py", ( "python scripts/generate_drop_authorization_payload.py --input " @@ -203,6 +206,7 @@ "docs/integrations/README.md", "docs/integrations/contract-flows.md", "docs/integrations/auction-flows.md", + "docs/integrations/wallets-and-signatures.md", "docs/architecture.md", "docs/threat-model.md", "docs/deployment.md", diff --git a/scripts/check_wallet_signature_flows.py b/scripts/check_wallet_signature_flows.py new file mode 100644 index 00000000..78573568 --- /dev/null +++ b/scripts/check_wallet_signature_flows.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +"""Validate the wallet and signature integration documentation.""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + + +DEFAULT_WALLET_SIGNATURE_FLOWS = Path("docs/integrations/wallets-and-signatures.md") + +REQUIRED_HEADINGS = [ + (1, "Wallets And Signatures"), + (2, "Maturity And Scope"), + (2, "Source Of Truth"), + (2, "Domain And Typed Data"), + (2, "Replay And Revocation Controls"), + (2, "EOA Wallet Flow"), + (2, "ERC-1271 Contract Signer Flow"), + (2, "Safe Signing Flow"), + (2, "WalletConnect And Mobile Handoff"), + (2, "Backend Signing Service Boundary"), + (2, "Frontend Preflight Reads"), + (2, "Failure States"), + (2, "Security And UX Requirements"), + (2, "Validation Commands"), + (2, "Maintenance"), +] + +REQUIRED_PHRASES = [ + "pre-audit", + "not production-ready", + "not a security claim", + "local baseline", + "does not replace fork/testnet/live evidence", + "public beta", + "production", + "React", + "mobile", + "Electron", + "operator UI", + "indexer", + "backend signing service", + "WalletConnect", + "Safe", + "EIP-712", + "ERC-1271", + "EIP-2098", + "DropAuthorization", + "mintDrop", + "domainSeparator", + "EIP712Domain", + "`name`", + "`version`", + "`chainId`", + "`verifyingContract`", + "6529StreamDrops", + "DROP_AUTHORIZATION_TYPEHASH", + "DROP_ID_TYPEHASH", + "DropId(address signer,uint256 signerEpoch,uint256 nonce,uint256 salt)", + "deriveDropId", + "hashDropAuthorization", + "tokenDataHash", + "keccak256(bytes(tokenData))", + "tdhSigner", + "signerEpoch", + "`nonce`", + "`salt`", + "`deadline`", + "consumedDropIds", + "cancelledDropIds", + "storage-backed", + "EIP-712 is encoding/signing only", + "no separate on-chain monotonic nonce map", + "signer-service obligation", + "wrong signer", + "wrong domain", + "wrong chain", + "expired deadline", + "replayed drop", + "cancelled drop", + "stale signer epoch", + "malleable signature", + "invalid `v`", + "zero recovered signer", + "malformed signature length", + "zero-address signer", + "zero-address recipient", + "non-zero auction recipient", + "token data substitution", + "65-byte", + "64-byte", + "isValidSignature(bytes32 digest, bytes signature)", + "0x1626ba7e", + "return exactly 32 bytes", + "invalid magic", + "empty return", + "short return", + "extra return", + "wrong digest", + "wrong signature bytes", + "signer custody readiness", + "no private keys", + "no-secret", + "eth_call", +] + +REQUIRED_COMMANDS = [ + "python scripts/test_wallet_signature_flows.py", + "python scripts/check_wallet_signature_flows.py", + "python scripts/test_integrations_readme.py", + "python scripts/check_integrations_readme.py", + "python scripts/test_release_readiness.py", + "python scripts/check_release_readiness.py", + "python scripts/test_drop_authorization_fixtures.py", + "python scripts/check_drop_authorization_fixtures.py", + "python scripts/test_drop_authorization_signing_evidence.py", + "python scripts/check_drop_authorization_signing_evidence.py", + "python scripts/test_signer_custody_readiness.py", + "python scripts/check_signer_custody_readiness.py", + "python scripts/check_changelog.py", + "python scripts/generate_release_manifest.py --check", + "python scripts/generate_bytecode_release_proof.py --check", + "python scripts/generate_release_checksums.py --check", + "forge test --match-path test/StreamDropsEIP712.t.sol", + "forge test --match-path test/StreamDropsERC1271.t.sol", +] + +REQUIRED_LINK_TARGETS = [ + "docs/integrations/README.md", + "docs/drop-authorization-signing.md", + "docs/adr/0001-drop-authorization.md", + "docs/signer-custody-readiness.md", + "docs/integrations/contract-flows.md", + "docs/integrations/auction-flows.md", + "docs/release-readiness.md", + "docs/non-local-release-evidence.md", + "docs/public-beta-evidence.md", + "release-artifacts/latest/public-beta-evidence.json", + "release-artifacts/latest/risk-register.json", + "release-artifacts/latest/release-manifest.json", + "release-artifacts/baselines/v0.1.0/abi-surface.json", + "release-artifacts/latest/abi-checksums.json", + "release-artifacts/latest/event-topic-catalog.json", + "release-artifacts/latest/interface-ids.json", + "deployments/address-books/anvil-6529stream-v0.1.0-001.json", + "deployments/address-books/fork-mainnet-6529stream-v0.1.0-001-broadcast.json", + "smart-contracts/StreamDrops.sol", + "smart-contracts/StreamAdmins.sol", + "test/StreamDropsEIP712.t.sol", + "test/StreamDropsERC1271.t.sol", + "test/helpers/DropAuthTestHelper.sol", + "test/fixtures/drop-authorization/fixed-price-eoa.json", + "test/fixtures/drop-authorization/auction-eoa.json", + "test/fixtures/drop-authorization/erc1271-contract-signer.json", + "test/fixtures/drop-authorization/payload-generator/fixed-price-output.json", + "test/fixtures/drop-authorization/payload-generator/auction-output.json", + "scripts/generate_drop_authorization_payload.py", + "scripts/check_drop_authorization_fixtures.py", + "scripts/check_drop_authorization_signing_evidence.py", + "scripts/check_signer_custody_readiness.py", +] + +HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.MULTILINE) +LINK_RE = re.compile(r"\[[^\]]+\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)") + + +class WalletSignatureFlowsError(ValueError): + """Raised when the wallet/signature flow doc is missing required content.""" + + +def normalize_repo_path(path: Path, repo_root: Path) -> str: + """Return a repository-relative POSIX path or reject path escapes.""" + try: + return path.resolve().relative_to(repo_root.resolve()).as_posix() + except ValueError as exc: + raise WalletSignatureFlowsError(f"linked path escapes repository: {path}") from exc + + +def markdown_headings(text: str) -> set[tuple[int, str]]: + """Extract Markdown headings as level/title pairs.""" + headings = set() + for match in HEADING_RE.finditer(text): + level = len(match.group(1)) + title = match.group(2).strip().rstrip("#").strip() + headings.add((level, title)) + return headings + + +def normalized_link_target(raw_target: str) -> str | None: + """Return a local Markdown link path without anchors or query strings.""" + target = raw_target.strip() + if not target or target.startswith("#"): + return None + if "://" in target or target.startswith("mailto:"): + return None + + path_part = target.split("#", 1)[0].split("?", 1)[0] + if not path_part: + return None + return path_part + + +def linked_repo_paths(repo_root: Path, document_path: Path, text: str) -> set[str]: + """Collect existing repository-relative file links from Markdown text.""" + links = set() + missing = [] + for match in LINK_RE.finditer(text): + target = normalized_link_target(match.group(1)) + if target is None: + continue + + target_path = Path(target) + if not target_path.is_absolute(): + target_path = document_path.parent / target_path + + resolved = target_path.resolve() + relative = normalize_repo_path(resolved, repo_root) + if not resolved.exists(): + missing.append(relative) + continue + links.add(relative) + + if missing: + raise WalletSignatureFlowsError( + "linked targets are missing: " + ", ".join(sorted(set(missing))) + ) + return links + + +def missing_phrases(text: str, phrases: list[str]) -> list[str]: + """Return required phrases that are absent from text, case-insensitively.""" + normalized_text = " ".join(text.lower().split()) + return [ + phrase + for phrase in phrases + if " ".join(phrase.lower().split()) not in normalized_text + ] + + +def validate_wallet_signature_flows(repo_root: Path, document_path: Path) -> None: + """Validate the wallet/signature integration documentation.""" + if not document_path.is_file(): + relative = normalize_repo_path(document_path, repo_root) + raise WalletSignatureFlowsError(f"missing wallet signature flows doc: {relative}") + + text = document_path.read_text(encoding="utf-8") + + headings = markdown_headings(text) + missing_headings = [ + f"{'#' * level} {title}" + for level, title in REQUIRED_HEADINGS + if (level, title) not in headings + ] + if missing_headings: + relative = normalize_repo_path(document_path, repo_root) + raise WalletSignatureFlowsError( + f"{relative} is missing required headings: " + + ", ".join(missing_headings) + ) + + missing_required_phrases = missing_phrases(text, REQUIRED_PHRASES) + if missing_required_phrases: + raise WalletSignatureFlowsError( + "wallet signature flows doc is missing required content: " + + ", ".join(missing_required_phrases) + ) + + missing_commands = [command for command in REQUIRED_COMMANDS if command not in text] + if missing_commands: + raise WalletSignatureFlowsError( + "wallet signature flows doc is missing required commands: " + + ", ".join(missing_commands) + ) + + links = linked_repo_paths(repo_root, document_path, text) + missing_targets = [target for target in REQUIRED_LINK_TARGETS if target not in links] + if missing_targets: + raise WalletSignatureFlowsError( + "wallet signature flows doc is missing required links: " + + ", ".join(missing_targets) + ) + + +def parse_args(argv: list[str]) -> argparse.Namespace: + """Parse wallet/signature checker options.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo-root", type=Path, default=Path.cwd()) + parser.add_argument( + "--wallet-signature-flows", + type=Path, + default=DEFAULT_WALLET_SIGNATURE_FLOWS, + ) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + """Run the wallet/signature checker CLI.""" + args = parse_args([] if argv is None else argv) + repo_root = args.repo_root.resolve() + document_path = args.wallet_signature_flows + if not document_path.is_absolute(): + document_path = repo_root / document_path + + try: + validate_wallet_signature_flows(repo_root, document_path.resolve()) + except WalletSignatureFlowsError as exc: + print(f"wallet signature flows check failed: {exc}", file=sys.stderr) + return 1 + + print("wallet signature flows doc is current") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/scripts/generate_release_manifest.py b/scripts/generate_release_manifest.py index 621c483b..320db17a 100644 --- a/scripts/generate_release_manifest.py +++ b/scripts/generate_release_manifest.py @@ -82,6 +82,7 @@ Path("docs/integrations/README.md"), Path("docs/integrations/contract-flows.md"), Path("docs/integrations/auction-flows.md"), + Path("docs/integrations/wallets-and-signatures.md"), Path("docs/tooling.md"), Path("docs/status.md"), ] diff --git a/scripts/test_integrations_readme.py b/scripts/test_integrations_readme.py index 932de098..70ac40fc 100644 --- a/scripts/test_integrations_readme.py +++ b/scripts/test_integrations_readme.py @@ -71,6 +71,7 @@ def minimal_integrations_readme() -> str: Future work: INT-002, INT-003, INT-004, INT-005, INT-006, INT-007, INT-008, and INT-009. The fixed-price mint flow is listed. The auction frontend and indexer flow spec is listed. +The wallet, EIP-712, ERC-1271, and Safe signing guide is listed. ## Readiness Boundaries diff --git a/scripts/test_release_manifest.py b/scripts/test_release_manifest.py index aeeeaeab..631f70b6 100644 --- a/scripts/test_release_manifest.py +++ b/scripts/test_release_manifest.py @@ -168,6 +168,7 @@ def seed_release_tree(root: Path) -> dict[str, Path]: root / "docs" / "integrations" / "README.md", root / "docs" / "integrations" / "contract-flows.md", root / "docs" / "integrations" / "auction-flows.md", + root / "docs" / "integrations" / "wallets-and-signatures.md", ] write_json( diff --git a/scripts/test_release_readiness.py b/scripts/test_release_readiness.py index 9bce8401..ac3f8d81 100644 --- a/scripts/test_release_readiness.py +++ b/scripts/test_release_readiness.py @@ -60,6 +60,7 @@ def minimal_release_readiness_doc() -> str: the integration entrypoint, the fixed-price mint and drop authorization flow spec, the auction frontend and indexer flow spec, +the wallet, EIP-712, ERC-1271, and Safe signing guide, the risk register, release evidence packet index, drop authorization signing fixtures, release evidence issue backlog, release evidence issue links, release evidence issue body sync, diff --git a/scripts/test_wallet_signature_flows.py b/scripts/test_wallet_signature_flows.py new file mode 100644 index 00000000..fecf9f66 --- /dev/null +++ b/scripts/test_wallet_signature_flows.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +"""Focused tests for the wallet/signature flow documentation checker.""" + +from __future__ import annotations + +import importlib.util +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from io import StringIO +from pathlib import Path + + +SCRIPT_PATH = Path(__file__).with_name("check_wallet_signature_flows.py") +SPEC = importlib.util.spec_from_file_location("check_wallet_signature_flows", SCRIPT_PATH) +assert SPEC is not None and SPEC.loader is not None +checker = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(checker) + + +def write_text(path: Path, value: str) -> None: + """Write UTF-8 text while creating parent directories.""" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(value, encoding="utf-8", newline="\n") + + +def seed_required_targets(root: Path) -> None: + """Create placeholder files for every required wallet/signature link target.""" + for relative in checker.REQUIRED_LINK_TARGETS: + write_text(root / relative, f"seed for {relative}\n") + + +def target_links() -> str: + """Render Markdown list items for all required link targets.""" + return "\n".join( + f"- [{target}](../../{target})" for target in checker.REQUIRED_LINK_TARGETS + ) + + +def minimal_wallet_signature_doc() -> str: + """Build the smallest wallet/signature doc accepted by the checker.""" + commands = "\n".join(checker.REQUIRED_COMMANDS) + links = target_links() + return f"""# Wallets And Signatures + +This pre-audit local baseline is not production-ready and not a security claim. +It does not replace fork/testnet/live evidence for public beta or production. + +## Maturity And Scope + +React, mobile, Electron, operator UI, indexer, backend signing service, +WalletConnect, and Safe teams are named. No private keys and no-secret policy +are named. + +## Source Of Truth + +{links} + +## Domain And Typed Data + +EIP-712, ERC-1271, DropAuthorization, mintDrop, domainSeparator, +EIP712Domain, `name`, `version`, `chainId`, `verifyingContract`, 6529StreamDrops, +DROP_AUTHORIZATION_TYPEHASH, DROP_ID_TYPEHASH, +DropId(address signer,uint256 signerEpoch,uint256 nonce,uint256 salt), +deriveDropId, hashDropAuthorization, tokenDataHash, +keccak256(bytes(tokenData)), tdhSigner, signerEpoch, `nonce`, `salt`, and `deadline` +are named. + +## Replay And Revocation Controls + +EIP-712 is encoding/signing only. Storage-backed consumedDropIds and +cancelledDropIds are named. There is no separate on-chain monotonic nonce map; +nonce uniqueness is a signer-service obligation. Wrong domain, wrong chain, +stale signer epoch, replayed drop, cancelled drop, and expired deadline. + +## EOA Wallet Flow + +65-byte and 64-byte EIP-2098 EOA signatures are named. Wrong signer, +malleable signature, invalid `v`, zero recovered signer, and malformed +signature length are named. + +## ERC-1271 Contract Signer Flow + +isValidSignature(bytes32 digest, bytes signature), 0x1626ba7e, +return exactly 32 bytes, invalid magic, empty return, short return, +extra return, wrong digest, and wrong signature bytes are named. + +## Safe Signing Flow + +Safe signer custody readiness is named. + +## WalletConnect And Mobile Handoff + +WalletConnect and mobile reconnect behavior are named. + +## Backend Signing Service Boundary + +Backend signing service boundaries are named. + +## Frontend Preflight Reads + +Frontend preflight reads and eth_call are named. + +## Failure States + +Zero-address signer, zero-address recipient, non-zero auction recipient, and +token data substitution are named. + +## Security And UX Requirements + +Security and UX requirements are named. + +## Validation Commands + +```sh +{commands} +``` + +## Maintenance + +Refresh when signature behavior changes. +""" + + +class WalletSignatureFlowsTests(unittest.TestCase): + def test_accepts_committed_doc(self) -> None: + """The committed wallet/signature guide satisfies the checker.""" + repo_root = Path(__file__).resolve().parents[1] + + with redirect_stdout(StringIO()), redirect_stderr(StringIO()): + result = checker.main(["--repo-root", str(repo_root)]) + + self.assertEqual(result, 0) + + def test_accepts_minimal_valid_doc(self) -> None: + """A minimal complete wallet/signature doc passes validation.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + write_text( + root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS, + minimal_wallet_signature_doc(), + ) + + with redirect_stdout(StringIO()), redirect_stderr(StringIO()): + result = checker.main(["--repo-root", str(root)]) + + self.assertEqual(result, 0) + + def test_accepts_custom_doc_path(self) -> None: + """The CLI accepts a non-default wallet/signature doc path.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + custom_path = Path("docs/custom-wallets/signatures.md") + write_text(root / custom_path, minimal_wallet_signature_doc()) + + with redirect_stdout(StringIO()), redirect_stderr(StringIO()): + result = checker.main( + [ + "--repo-root", + str(root), + "--wallet-signature-flows", + str(custom_path), + ] + ) + + self.assertEqual(result, 0) + + def test_rejects_missing_heading(self) -> None: + """Missing required headings are rejected.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + text = minimal_wallet_signature_doc().replace("## Safe Signing Flow\n", "") + write_text(root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS, text) + + with self.assertRaisesRegex( + checker.WalletSignatureFlowsError, "missing required headings" + ): + checker.validate_wallet_signature_flows( + root, root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS + ) + + def test_rejects_missing_required_phrase(self) -> None: + """Missing signature safety language is rejected.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + text = minimal_wallet_signature_doc().replace( + "EIP-712 is encoding/signing only", "EIP-712 is enough" + ) + write_text(root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS, text) + + with self.assertRaisesRegex( + checker.WalletSignatureFlowsError, "missing required content" + ): + checker.validate_wallet_signature_flows( + root, root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS + ) + + def test_required_phrases_tolerate_markdown_wrapping(self) -> None: + """Required phrases may span Markdown-wrapped lines.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + text = minimal_wallet_signature_doc().replace( + "does not replace fork/testnet/live evidence", + "does not replace fork/testnet/live\nevidence", + ) + write_text(root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS, text) + + with redirect_stdout(StringIO()), redirect_stderr(StringIO()): + result = checker.main(["--repo-root", str(root)]) + + self.assertEqual(result, 0) + + def test_rejects_missing_required_link(self) -> None: + """Required source links cannot be silently dropped.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + original = minimal_wallet_signature_doc() + text = original.replace( + "- [smart-contracts/StreamDrops.sol](../../smart-contracts/StreamDrops.sol)\n", + "", + ) + self.assertNotEqual(text, original, "replacement had no effect") + write_text(root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS, text) + + with self.assertRaisesRegex( + checker.WalletSignatureFlowsError, "missing required links" + ): + checker.validate_wallet_signature_flows( + root, root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS + ) + + def test_rejects_missing_linked_file(self) -> None: + """Local links must resolve to existing repository files.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + (root / "smart-contracts/StreamDrops.sol").unlink() + write_text( + root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS, + minimal_wallet_signature_doc(), + ) + + with self.assertRaisesRegex( + checker.WalletSignatureFlowsError, "linked targets are missing" + ): + checker.validate_wallet_signature_flows( + root, root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS + ) + + def test_rejects_missing_required_command(self) -> None: + """Validation commands must stay visible in the doc.""" + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + seed_required_targets(root) + text = minimal_wallet_signature_doc().replace( + "python scripts/check_wallet_signature_flows.py\n", "" + ) + write_text(root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS, text) + + with self.assertRaisesRegex( + checker.WalletSignatureFlowsError, "missing required commands" + ): + checker.validate_wallet_signature_flows( + root, root / checker.DEFAULT_WALLET_SIGNATURE_FLOWS + ) + + +if __name__ == "__main__": + unittest.main(verbosity=2)