From 8b608fe57f9beead3d71bd0507070bd7c382deb3 Mon Sep 17 00:00:00 2001 From: Liviu Date: Wed, 30 Sep 2026 00:48:10 +0300 Subject: [PATCH] agents/x402: a 2-of-2 contract wallet (ERC-1271) that neither the agent nor its owner can spend from alone AereAgentWallet2of2 holds the agent's tokens and accepts only the agent's signature followed by the policy service's, over the same digest. In the new cosign mode the wallet service signs only its half, after every check it already made, and the agent adds its half only after it recomputes the payment itself (payer, recipient, amount, the nonce of its own ledger entry, validity, digest, declared policy signer). verifica-plati.mjs requires the wallet's code on chain to be exactly the compiled contract with the two signers; recompileaza-contract.mjs recompiles the published artifact byte for byte with solc 0.8.23. Tests: co-signing 16/16, payment verifier 14/14, negative control 30/30, wallet 25/25. On the public testnet 28001 on 2026-09-29: 13/13 with the 2-of-2 wallet (the agent alone and the policy signer alone refused by the facilitator and by the token asked on chain) and 9/9 with the wallet key. Evidence in dovezi-28001/. Nothing here has been run on the Aere Network mainnet. --- README.md | 6 +- agents/README.md | 6 +- agents/x402/AereAgentWallet2of2.json | 165 ++++++++++++++ agents/x402/README.md | 44 +++- agents/x402/client.mjs | 42 +++- agents/x402/contract-2of2.mjs | 37 ++++ agents/x402/contract/AereAgentWallet2of2.sol | 53 +++++ agents/x402/control-negativ-wallet.mjs | 23 +- .../plati-agent-2026-09-29-21-42-10.json | 164 ++++++++++++++ .../plati-agent-2of2-2026-09-29-21-29-48.json | 171 +++++++++++++++ .../rpc-inregistrat-2026-09-29-21-42-10.json | 205 +++++++++++++++++ ...-inregistrat-2of2-2026-09-29-21-29-48.json | 206 ++++++++++++++++++ agents/x402/facilitator-local.mjs | 16 +- agents/x402/inregistreaza-rpc.mjs | 7 +- agents/x402/proba-cosign-testnet.mjs | 145 ++++++++++++ agents/x402/proba-cosign.mjs | 98 +++++++++ agents/x402/proba-verifica-plati.mjs | 26 ++- agents/x402/recompileaza-contract.mjs | 52 +++++ agents/x402/verifica-plati.mjs | 17 +- agents/x402/wallet.mjs | 37 +++- 20 files changed, 1480 insertions(+), 40 deletions(-) create mode 100644 agents/x402/AereAgentWallet2of2.json create mode 100644 agents/x402/contract-2of2.mjs create mode 100644 agents/x402/contract/AereAgentWallet2of2.sol create mode 100644 agents/x402/dovezi-28001/plati-agent-2026-09-29-21-42-10.json create mode 100644 agents/x402/dovezi-28001/plati-agent-2of2-2026-09-29-21-29-48.json create mode 100644 agents/x402/dovezi-28001/rpc-inregistrat-2026-09-29-21-42-10.json create mode 100644 agents/x402/dovezi-28001/rpc-inregistrat-2of2-2026-09-29-21-29-48.json create mode 100644 agents/x402/proba-cosign-testnet.mjs create mode 100644 agents/x402/proba-cosign.mjs create mode 100644 agents/x402/recompileaza-contract.mjs diff --git a/README.md b/README.md index 4bed869..5fe4787 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ notarization command of the verification layer, and the agents' x402 wallet (EIP | [`proof-kinds/`](proof-kinds/) | the AIP-23 envelope builder the verification layer uses: fourteen proof kinds, one envelope format, digests instead of raw content | | [`readiness/`](readiness/) | the post-quantum readiness scanner of a public hostname: real TLS handshakes (hybrid only, hybrid preferred, TLS 1.2), HSTS, the certificate; no connection to an address not proven public; a rate limit per client that X-Forwarded-For cannot bypass | | [`control-plane/`](control-plane/) | from findings to a finished migration: a prioritized plan from the inventory and the scanner, its execution through the gateway, KMS and PKI with consent per action and a measured proof afterwards, recipes for the servers the products do not touch and a rescan that judges them, a compliance report against NIST IR 8547, the EU roadmap and optionally CNSA 2.0, and a console that checks it all again | -| [`agents/`](agents/) | limits an AI agent cannot break unseen: a post-quantum identity (ML-DSA-65), a policy (spending per time window, allowed tools and recipients, which actions need human approval), a signed ledger of every action judged against the policy, approvals and revocation signed by people, and a verifier that re-runs the policy over the whole ledger without trusting the agent; two branches of one ledger are a proof of equivocation anyone can check; and a wallet that pays over x402 only what the agent's ledger records and its policy allows, run on the public testnet with its evidence | +| [`agents/`](agents/) | limits an AI agent cannot break unseen: a post-quantum identity (ML-DSA-65), a policy (spending per time window, allowed tools and recipients, which actions need human approval), a signed ledger of every action judged against the policy, approvals and revocation signed by people, and a verifier that re-runs the policy over the whole ledger without trusting the agent; two branches of one ledger are a proof of equivocation anyone can check; and a wallet that pays over x402 only what the agent's ledger records and its policy allows, either holding the payment key for the owner or co-signing from a 2-of-2 contract wallet that neither the agent nor the owner can spend alone; both run on the public testnet with their evidence | Each component's README says what it is **not** and what is **not measured**. No third party has reviewed any of them. @@ -34,7 +34,7 @@ test, counts as a failure of the control. Results measured on 2026-09-29 (Node.j | proof-kinds | 24/24 with the same verifier (`AERE_VERIFY_PROOF=... node proba-proof-kinds.mjs`) | six negative controls inside the test | | readiness | 6/6 (`node proba-adrese-private.mjs`: the private-address rules, and a local listener no scan may touch) | the rate limit and the queue bound are tested where the service runs, not here (its README says so) | | control-plane | planner 30/30, command line 9/9, execution 30/30 on real products started locally, remediation 33/33 on real TLS servers, compliance report 27/27 (with the AIP-23 verifier), console 8/8 | remediation 7/7, compliance report 3/3 in this repository | -| agents | policy 27/27 with the AIP-23 verifier (without it 25 run, 2 are reported as skipped and the exit code is 2), ledger 51/51, approval and revocation 39/39, command line 23/23 through files and processes only (on Linux and macOS one more test checks the key file mode; not measured here); x402 wallet 25/25 and payment verifier 10/10 without a network; on the public testnet 28001, 9/9 (`x402/proba-x402-testnet.mjs`, needs a funded testnet key) | 26/26 (`node control-negativ-aprobare.mjs`); x402 21/21 (`node x402/control-negativ-wallet.mjs`) | +| agents | policy 27/27 with the AIP-23 verifier (without it 25 run, 2 are reported as skipped and the exit code is 2), ledger 51/51, approval and revocation 39/39, command line 23/23 through files and processes only (on Linux and macOS one more test checks the key file mode; not measured here); x402 wallet 25/25, 2-of-2 co-signing 16/16 and payment verifier 14/14 without a network; on the public testnet 28001, 9/9 with the wallet key and 13/13 with the 2-of-2 contract wallet (`x402/proba-x402-testnet.mjs`, `x402/proba-cosign-testnet.mjs`, each needs a funded testnet key); the contract's artifact recompiles byte for byte with solc 0.8.23 (`node x402/recompileaza-contract.mjs --solc `) | 26/26 (`node control-negativ-aprobare.mjs`); x402 30/30 (`node x402/control-negativ-wallet.mjs`); the contract's own tests (7) and their negative control (4/4) run in the Aere Network contracts project, not in this repository | Code comments, most function and variable names (also many exported between the files of a component), test names and control messages are in Romanian, and so are the two command words of the KMS HSM tool (explained in its README). Error codes, error @@ -43,4 +43,4 @@ interface, command line and data (`definePolicy`, `verifyLedger`, `approve`, ... ## Licence -MIT, see [LICENSE](LICENSE). Files: 122 (pq-gateway 6, pq-kms 10, pq-pki 6, crypto-inventory 42, verify-layer 8, proof-kinds 3, control-plane 17, agents 24, readiness 4). +MIT, see [LICENSE](LICENSE). Files: 132 (pq-gateway 6, pq-kms 10, pq-pki 6, crypto-inventory 42, verify-layer 8, proof-kinds 3, control-plane 17, agents 34, readiness 4). diff --git a/agents/README.md b/agents/README.md index b224962..3d09fa7 100644 --- a/agents/README.md +++ b/agents/README.md @@ -55,8 +55,10 @@ printed; the tool creates them with mode 0600, which has no effect on Windows (p ## Paying over x402 `x402/` holds the agent wallet: a service that keeps the payment key for the owner and signs an x402 payment only when the agent's -ledger records it and the policy allows it, with the wallet as the witness of the ledger's heads and time. It was run end to end -on the public testnet 28001 on 2026-09-29, and the evidence is there with a verifier anyone can run. See `x402/README.md`. +ledger records it and the policy allows it, with the wallet as the witness of the ledger's heads and time. With a 2-of-2 contract +wallet (`AereAgentWallet2of2`, ERC-1271) the money leaves only with the agent's signature and the wallet service's together, so +neither the agent nor the owner can spend alone. Both were run end to end on the public testnet 28001 on 2026-09-29, and the +evidence is there with a verifier anyone can run. See `x402/README.md`. ## What the verifier can and cannot see diff --git a/agents/x402/AereAgentWallet2of2.json b/agents/x402/AereAgentWallet2of2.json new file mode 100644 index 0000000..b44853c --- /dev/null +++ b/agents/x402/AereAgentWallet2of2.json @@ -0,0 +1,165 @@ +{ + "v": 1, + "kind": "aere-agent-wallet-2of2-artifact", + "contractName": "AereAgentWallet2of2", + "sourcePath": "contracts/x402/AereAgentWallet2of2.sol", + "sourceSha256": "dcdc02a7bd5b94b203a4aa889a4fac10d06f4aa24fe8e6c88d2a1ecfd582de03", + "compiler": { + "solc": "0.8.23+commit.f704f362", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1 + }, + "viaIR": true, + "evmVersion": "paris" + } + }, + "standardJsonInput": { + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/interfaces/IERC1271.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC1271 standard signature validation method for\n * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].\n *\n * _Available since v4.1._\n */\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with _data\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);\n}\n" + }, + "@openzeppelin/contracts/utils/cryptography/ECDSA.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Strings.sol\";\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV // Deprecated in v4.8\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n /// @solidity memory-safe-assembly\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n รท 2 + 1, and for v in (302): v โˆˆ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n /// @solidity memory-safe-assembly\n assembly {\n mstore(0x00, \"\\x19Ethereum Signed Message:\\n32\")\n mstore(0x1c, hash)\n message := keccak256(0x00, 0x3c)\n }\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {\n /// @solidity memory-safe-assembly\n assembly {\n let ptr := mload(0x40)\n mstore(ptr, \"\\x19\\x01\")\n mstore(add(ptr, 0x02), domainSeparator)\n mstore(add(ptr, 0x22), structHash)\n data := keccak256(ptr, 0x42)\n }\n }\n\n /**\n * @dev Returns an Ethereum Signed Data with intended validator, created from a\n * `validator` and `data` according to the version 0 of EIP-191.\n *\n * See {recover}.\n */\n function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x00\", validator, data));\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Down, // Toward negative infinity\n Up, // Toward infinity\n Zero // Toward zero\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a > b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a == 0 ? 0 : (a - 1) / b + 1;\n }\n\n /**\n * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0\n * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)\n * with further edits by Uniswap Labs also under MIT license.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n unchecked {\n // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\n // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = prod1 * 2^256 + prod0.\n uint256 prod0; // Least significant 256 bits of the product\n uint256 prod1; // Most significant 256 bits of the product\n assembly {\n let mm := mulmod(x, y, not(0))\n prod0 := mul(x, y)\n prod1 := sub(sub(mm, prod0), lt(mm, prod0))\n }\n\n // Handle non-overflow cases, 256 by 256 division.\n if (prod1 == 0) {\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n // The surrounding unchecked block does not change this fact.\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n return prod0 / denominator;\n }\n\n // Make sure the result is less than 2^256. Also prevents denominator == 0.\n require(denominator > prod1, \"Math: mulDiv overflow\");\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [prod1 prod0].\n uint256 remainder;\n assembly {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n prod1 := sub(prod1, gt(remainder, prod0))\n prod0 := sub(prod0, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.\n // See https://cs.stackexchange.com/q/138556/92363.\n\n // Does not overflow because the denominator cannot be zero at this stage in the function.\n uint256 twos = denominator & (~denominator + 1);\n assembly {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [prod1 prod0] by twos.\n prod0 := div(prod0, twos)\n\n // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from prod1 into prod0.\n prod0 |= prod1 * twos;\n\n // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\n // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv = 1 mod 2^4.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works\n // in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2^8\n inverse *= 2 - denominator * inverse; // inverse mod 2^16\n inverse *= 2 - denominator * inverse; // inverse mod 2^32\n inverse *= 2 - denominator * inverse; // inverse mod 2^64\n inverse *= 2 - denominator * inverse; // inverse mod 2^128\n inverse *= 2 - denominator * inverse; // inverse mod 2^256\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\n // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\n // is no longer required.\n result = prod0 * inverse;\n return result;\n }\n }\n\n /**\n * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n uint256 result = mulDiv(x, y, denominator);\n if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {\n result += 1;\n }\n return result;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.\n *\n * Inspired by Henry S. Warren, Jr.'s \"Hacker's Delight\" (Chapter 11).\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n }\n\n // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\n //\n // We know that the \"msb\" (most significant bit) of our target number `a` is a power of 2 such that we have\n // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\n //\n // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\n // โ†’ `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\n // โ†’ `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\n //\n // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\n uint256 result = 1 << (log2(a) >> 1);\n\n // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\n // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\n // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\n // into the expected uint128 result.\n unchecked {\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n return min(result, a / result);\n }\n }\n\n /**\n * @notice Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 2, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 128;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 64;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 32;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 16;\n }\n if (value >> 8 > 0) {\n value >>= 8;\n result += 8;\n }\n if (value >> 4 > 0) {\n value >>= 4;\n result += 4;\n }\n if (value >> 2 > 0) {\n value >>= 2;\n result += 2;\n }\n if (value >> 1 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 10, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10 ** 64) {\n value /= 10 ** 64;\n result += 64;\n }\n if (value >= 10 ** 32) {\n value /= 10 ** 32;\n result += 32;\n }\n if (value >= 10 ** 16) {\n value /= 10 ** 16;\n result += 16;\n }\n if (value >= 10 ** 8) {\n value /= 10 ** 8;\n result += 8;\n }\n if (value >= 10 ** 4) {\n value /= 10 ** 4;\n result += 4;\n }\n if (value >= 10 ** 2) {\n value /= 10 ** 2;\n result += 2;\n }\n if (value >= 10 ** 1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 256, rounded down, of a positive value.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 16;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 8;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 4;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 2;\n }\n if (value >> 8 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SignedMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Standard signed math utilities missing in the Solidity language.\n */\nlibrary SignedMath {\n /**\n * @dev Returns the largest of two signed numbers.\n */\n function max(int256 a, int256 b) internal pure returns (int256) {\n return a > b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two signed numbers.\n */\n function min(int256 a, int256 b) internal pure returns (int256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two signed numbers without overflow.\n * The result is rounded towards zero.\n */\n function average(int256 a, int256 b) internal pure returns (int256) {\n // Formula from the book \"Hacker's Delight\"\n int256 x = (a & b) + ((a ^ b) >> 1);\n return x + (int256(uint256(x) >> 255) & (a ^ b));\n }\n\n /**\n * @dev Returns the absolute unsigned value of a signed value.\n */\n function abs(int256 n) internal pure returns (uint256) {\n unchecked {\n // must be unchecked in order to support `n = type(int256).min`\n return uint256(n >= 0 ? n : -n);\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Strings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./math/Math.sol\";\nimport \"./math/SignedMath.sol\";\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n /// @solidity memory-safe-assembly\n assembly {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n /// @solidity memory-safe-assembly\n assembly {\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `int256` to its ASCII `string` decimal representation.\n */\n function toString(int256 value) internal pure returns (string memory) {\n return string(abi.encodePacked(value < 0 ? \"-\" : \"\", toString(SignedMath.abs(value))));\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n\n /**\n * @dev Returns true if the two strings are equal.\n */\n function equal(string memory a, string memory b) internal pure returns (bool) {\n return keccak256(bytes(a)) == keccak256(bytes(b));\n }\n}\n" + }, + "contracts/x402/AereAgentWallet2of2.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.23;\n\nimport {ECDSA} from \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport {IERC1271} from \"@openzeppelin/contracts/interfaces/IERC1271.sol\";\n\n/**\n * @title AereAgentWallet2of2, an AI agent's payment wallet that neither the agent nor its owner can spend from alone\n *\n * @notice The wallet holds tokens and has no function that moves them. Tokens leave it only through a token that asks it,\n * by ERC-1271, whether a signature over a digest is valid (EIP-3009 transferWithAuthorization in the testnet token\n * AereTestUSD3009 does that for a contract `from`). The answer is yes only for a 130-byte signature that is the agent's\n * secp256k1 signature followed by the policy signer's, both over that digest, both non-malleable (OpenZeppelin ECDSA).\n *\n * The agent holds the first key. The owner's policy service holds the second and co-signs a payment only when the\n * agent's post-quantum ledger records it and the owner's policy allows it (tools/agent-policy/x402/wallet.mjs, the\n * `cosign` mode). So a stolen agent key cannot spend past the policy, and the owner cannot spend without the agent.\n *\n * HONEST SCOPE. Both signers are fixed at deployment; there is no rotation, no recovery and no owner: if either key\n * is lost, the tokens stay in the wallet. The agent's key here is classical (secp256k1); the post-quantum binding is\n * the ledger the policy service checks before it co-signs, not this contract. Any digest both keys sign is valid, as\n * in any 2-of-2 multisig: the software signs only EIP-3009 digests. Written 2026-09-29 for the public testnet 28001;\n * not deployed on chain 2800 (that is the founder's decision).\n */\ncontract AereAgentWallet2of2 is IERC1271 {\n bytes4 private constant MAGIC = 0x1626ba7e;\n bytes4 private constant INVALID = 0xffffffff;\n\n /// @notice the agent's key (first 65 bytes of a valid signature)\n address public immutable agentSigner;\n /// @notice the owner's policy service key (last 65 bytes of a valid signature)\n address public immutable policySigner;\n\n error ZeroSigner();\n error SameSigner();\n\n constructor(address agent, address policy) {\n if (agent == address(0) || policy == address(0)) revert ZeroSigner();\n if (agent == policy) revert SameSigner();\n agentSigner = agent;\n policySigner = policy;\n }\n\n /// @inheritdoc IERC1271\n function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4) {\n if (signature.length != 130) return INVALID;\n (address a, ECDSA.RecoverError ea) = ECDSA.tryRecover(hash, signature[0:65]);\n if (ea != ECDSA.RecoverError.NoError || a != agentSigner) return INVALID;\n (address p, ECDSA.RecoverError ep) = ECDSA.tryRecover(hash, signature[65:130]);\n if (ep != ECDSA.RecoverError.NoError || p != policySigner) return INVALID;\n return MAGIC;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 1 + }, + "viaIR": true, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ], + "": [ + "ast" + ] + } + } + } + }, + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "agent", + "type": "address" + }, + { + "internalType": "address", + "name": "policy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "SameSigner", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroSigner", + "type": "error" + }, + { + "inputs": [], + "name": "agentSigner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "policySigner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x60c0346100d357601f6104a938819003918201601f19168301916001600160401b038311848410176100d85780849260409485528339810103126100d357610052602061004b836100ee565b92016100ee565b906001600160a01b0380821690811580156100c9575b6100b7578316146100a65760805260a0526040516103a6908161010382396080518181816053015261027d015260a051818181609b015261021f0152f35b6040516277576960e81b8152600490fd5b60405163e5c48ac560e01b8152600490fd5b5080841615610068565b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036100d35756fe6080604052600436101561001257600080fd5b6000803560e01c80631626ba7e146100ca57806338eab6b9146100855763f4e2592b1461003e57600080fd5b346100825780600319360112610082576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b80fd5b50346100825780600319360112610082576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5034610082576040366003190112610082576001600160401b03906024359082821161008257366023830112156100825781600401359283116100825736602484840101116100825760206101258460248501600435610193565b6040516001600160e01b03199091168152f35b60405191929190608082016001600160401b0381118382101761017d57604052819360418352604182011161017857816041606192602060009501370152565b600080fd5b634e487b7160e01b600052604160045260246000fd5b90916082810361026b5780604111610178576101b86101b23685610138565b836102b3565b600581101561025557159081159161027b575b5061026b57608211610178576101e86101ee926041369101610138565b906102b3565b600581101561025557159081159161021d575b5061021157630b135d3f60e11b90565b6001600160e01b031990565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161415905038610201565b634e487b7160e01b600052602160045260246000fd5b506001600160e01b031992915050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03908116911614159050386101cb565b9060418151146000146102e1576102dd916020820151906060604084015193015160001a906102eb565b9091565b5050600090600290565b9291906fa2a8918ca85bafe22016d0b997e4df60600160ff1b0383116103645791608094939160ff602094604051948552168484015260408301526060820152600093849182805260015afa156103575781516001600160a01b03811615610351579190565b50600190565b50604051903d90823e3d90fd5b5050505060009060039056fea2646970667358221220c1859b9ad2a87f5981b6e6ade9d1980dabedd70235a69d674774c6603ec6ceac64736f6c63430008170033", + "deployedBytecode": "0x6080604052600436101561001257600080fd5b6000803560e01c80631626ba7e146100ca57806338eab6b9146100855763f4e2592b1461003e57600080fd5b346100825780600319360112610082576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b80fd5b50346100825780600319360112610082576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5034610082576040366003190112610082576001600160401b03906024359082821161008257366023830112156100825781600401359283116100825736602484840101116100825760206101258460248501600435610193565b6040516001600160e01b03199091168152f35b60405191929190608082016001600160401b0381118382101761017d57604052819360418352604182011161017857816041606192602060009501370152565b600080fd5b634e487b7160e01b600052604160045260246000fd5b90916082810361026b5780604111610178576101b86101b23685610138565b836102b3565b600581101561025557159081159161027b575b5061026b57608211610178576101e86101ee926041369101610138565b906102b3565b600581101561025557159081159161021d575b5061021157630b135d3f60e11b90565b6001600160e01b031990565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161415905038610201565b634e487b7160e01b600052602160045260246000fd5b506001600160e01b031992915050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03908116911614159050386101cb565b9060418151146000146102e1576102dd916020820151906060604084015193015160001a906102eb565b9091565b5050600090600290565b9291906fa2a8918ca85bafe22016d0b997e4df60600160ff1b0383116103645791608094939160ff602094604051948552168484015260408301526060820152600093849182805260015afa156103575781516001600160a01b03811615610351579190565b50600190565b50604051903d90823e3d90fd5b5050505060009060039056fea2646970667358221220c1859b9ad2a87f5981b6e6ade9d1980dabedd70235a69d674774c6603ec6ceac64736f6c63430008170033", + "immutables": { + "agentSigner": [ + { + "start": 83, + "length": 32 + }, + { + "start": 637, + "length": 32 + } + ], + "policySigner": [ + { + "start": 155, + "length": 32 + }, + { + "start": 543, + "length": 32 + } + ] + } +} diff --git a/agents/x402/README.md b/agents/x402/README.md index 949cc38..8135594 100644 --- a/agents/x402/README.md +++ b/agents/x402/README.md @@ -5,6 +5,10 @@ agent does **not** hold the payment key. A small service, the **agent wallet**, when the agent has written that payment into its signed ledger (`../agent-ledger.mjs`) and the owner's policy allows it. The agent authenticates to the wallet with the ledger entry itself, which only its ML-DSA-65 key can sign. +There are two modes. In the first (`eoa`), the wallet's address is its own key, so the owner who holds that key could also pay without +the agent. In the second (`cosign`), the money sits in a **2-of-2 contract wallet** (`AereAgentWallet2of2`, ERC-1271) that pays only +with the agent's signature **and** the wallet service's signature: neither the agent nor the owner can spend alone. + Node.js 24 and `ethers` (for EIP-712 and secp256k1: `npm install` here). Payments follow x402 version 2 (`exact` scheme, EIP-3009 `transferWithAuthorization`), with the HTTP headers `PAYMENT-REQUIRED`, `PAYMENT-SIGNATURE` and `PAYMENT-RESPONSE`. @@ -45,10 +49,33 @@ before a signature is returned. The agent side is `payWithAgent({ url, ledger, w talks to the service. `resource-server.mjs` is a minimal x402 seller for tests; `facilitator-local.mjs` makes the same checks as an x402 facilitator, in memory, for tests without a chain. +## The 2-of-2 contract wallet (`cosign` mode) + +`contract/AereAgentWallet2of2.sol` (in the published package; compiled in `AereAgentWallet2of2.json`) holds the agent's tokens and has +no function that moves them. A token that checks signatures with ERC-1271, like the testnet's EIP-3009 token, asks it whether a +signature over a digest is valid, and it answers yes only for 130 bytes: the agent's secp256k1 signature followed by the policy +signer's, both over that digest. Both signers are fixed when it is deployed; there is no owner, no rotation and no recovery. + +- The wallet service is started with `contractWallet: ` and the **policy signer's** key. It makes every check above + and returns its half of the signature with the digest, not a payment header. +- The agent (`payWithAgent({ ..., agentEvmKey })`, `completeazaCosemnarea` in `client.mjs`) adds its half only after it recomputes the + payment itself: from the contract, to the seller and for the amount of the purchase, with the nonce of **its own** ledger entry, a + bounded validity, the same digest, and a policy half that recovers to the declared policy signer. A compromised wallet service cannot + make the agent sign another payment. +- `verifica-plati.mjs`, given an evidence file with `walletContract: { agentSigner, policySigner }`, also requires the wallet's code on + chain to be **exactly** the compiled contract with those two signers in place of its immutables. A contract that only answers + `agentSigner()` could lie; its bytes cannot. +- `node recompileaza-contract.mjs --solc ` compiles the artifact's standard input again and requires the creation and + runtime code to come out byte for byte, and the readable source to be the compiled one; a comment changed by one character must + give other code, or the check reports that it cannot tell. `--solcjs` runs `npx solc@0.8.23` instead (not measured here). + ## What it does not do -- It is not trustless custody: whoever holds the wallet key (the owner) can pay without the agent. A 2-of-2 contract wallet - (the agent and the policy service, ERC-1271, which the testnet token accepts) would remove that trust; it is not built. +- In `eoa` mode it is not trustless custody: whoever holds the wallet key (the owner) can pay without the agent. The `cosign` mode + removes that; its contract has no recovery, so if either key is lost the tokens stay in it. +- In both modes the payment signatures are classical (secp256k1): EIP-3009 requires it from a key, and this contract checks + secp256k1 too. A contract could check a post-quantum signature through ERC-1271 instead; this one does not. What is post-quantum + is the agent's identity and ledger (ML-DSA-65), which the wallet service checks before it signs. - An authorization that is signed but never settled still counts against the limit until the window passes (conservative). - The wallet API must be reached over a trusted channel: an entry intercepted on the way could be submitted by someone else, who would then receive the payment payload (the money still goes to the seller the entry names). @@ -62,14 +89,21 @@ Measured on 2026-09-29 (Node.js 24.14.1, ethers 6.16.0): | test | result | |---|---| | `node proba-wallet.mjs` | 25/25 without a network, real EIP-712 signatures and ML-DSA-65 keys: five paid purchases through a local facilitator, the sixth refused by the policy; attacks by the holder of the agent key (a forged "allowed" entry over the limit, an entry for another amount, the same entry twice, a branch with its equivocation proof, a new ledger under a changed policy, a backdated entry, two branches sent at the same time), the owner's revocation, human approval, a payment for another requirement and a replayed payment at the seller | -| `node proba-verifica-plati.mjs` | 10/10 without a network, on the chain responses recorded for the testnet run below | -| `node control-negativ-wallet.mjs` | 21/21: each guard of the wallet, the client, the seller and the verifier removed in a copy, and the named check must fail | +| `node proba-cosign.mjs` | 16/16 without a network (the contract's ERC-1271 check emulated in the local facilitator): three purchases from a 2-of-2 wallet with both halves in order, the fourth refused by the policy; the agent alone, and the policy signer alone, cannot pay; a compromised wallet service that changes the recipient, the payer, the amount, the nonce, the validity, the digest, or signs with another key: the agent refuses to co-sign; the co-signing service over HTTP | +| `node proba-verifica-plati.mjs` | 14/14 without a network, on the chain responses recorded for both testnet runs below, including the contract's code (one byte changed, another signer named, or no code at the wallet: `INVALID`) | +| `node control-negativ-wallet.mjs` | 30/30: each guard of the wallet, the client (including the agent's co-signing checks), the seller and the verifier (including the contract code) removed in a copy, and the named check must fail | | `AERE_TESTNET_KEY_FILE= node proba-x402-testnet.mjs` | 9/9 on the public testnet 28001 through its x402 facilitator: a fresh wallet funded with 0.05 tUSD, three purchases of 0.01 paid and settled on chain, the fourth refused by the policy, a replayed payment refused, the balances and the used authorization nonces read on chain, and the evidence file verified `VALID` (a changed amount, or a payment removed from the file: `INVALID`) | +| `AERE_TESTNET_KEY_FILE= node proba-cosign-testnet.mjs` | 13/13 on the public testnet 28001: an `AereAgentWallet2of2` deployed from `AereAgentWallet2of2.json` with two fresh keys, its code on chain exactly the compiled code; `isValidSignature` on chain yes only for agent then policy signer, no for seven other forms; three purchases of 0.01 from it through the testnet facilitator, the fourth refused by the policy; the agent alone and the policy signer alone refused by the facilitator, and by the token itself asked on chain without the facilitator; balances and nonces on chain; the evidence verified `VALID` with the contract code (another policy signer named: `INVALID`) | +| `node recompileaza-contract.mjs --solc ` | `IDENTICAL`: 1193 bytes of creation code and 934 of runtime code, with the native solc 0.8.23+commit.f704f362 | -The testnet run of 2026-09-29 is in `dovezi-28001/`: the evidence file and the recorded chain responses. Anyone can check it: +The contract's own tests (7, hardhat, with the testnet token) and their negative control (each of its four guards removed in a copy, +the named test must fail: 4/4) run in the Aere Network contracts project and are not part of this package. + +The testnet runs of 2026-09-29 are in `dovezi-28001/`: the evidence files and the recorded chain responses. Anyone can check them: ``` node verifica-plati.mjs dovezi-28001/plati-agent-2026-09-29-20-23-16.json --rpc https://testnet-rpc.aere.network --all-transfers +node verifica-plati.mjs dovezi-28001/plati-agent-2of2-2026-09-29-21-29-48.json --rpc https://testnet-rpc.aere.network --all-transfers ``` tUSD is the testnet's EIP-3009 token, minted by a faucet and worth nothing. Nothing here has been run on the Aere Network mainnet. diff --git a/agents/x402/client.mjs b/agents/x402/client.mjs index a7ddb23..ab76dc9 100644 --- a/agents/x402/client.mjs +++ b/agents/x402/client.mjs @@ -2,11 +2,38 @@ // registrul agentului, apoi cerand portofelului (wallet.mjs) semnatura, apoi reluand cererea cu antetul PAYMENT-SIGNATURE. Numele // antetelor si forma lor sunt ale specificatiei x402 v2 (transportul HTTP): PAYMENT-REQUIRED, PAYMENT-SIGNATURE, PAYMENT-RESPONSE, toate // JSON in base64. Daca politica refuza plata, clientul se opreste inainte de portofel (si portofelul ar refuza oricum). -import { x402Action } from './wallet.mjs'; +import { x402Action, nonceForEntry, incarcaEthers, EIP3009_TYPES } from './wallet.mjs'; export const b64json = (o) => Buffer.from(JSON.stringify(o), 'utf8').toString('base64'); export function dinB64json(s) { try { return JSON.parse(Buffer.from(String(s), 'base64').toString('utf8')); } catch { return null; } } +/** + * Modul cosign (portofel-contract 2-din-2): agentul isi adauga jumatatea de semnatura NUMAI dupa ce recalculeaza singur ce semneaza. + * Un serviciu de politica compromis nu poate face agentul sa semneze alta plata decat cea din intrarea lui: se cer platitorul (contractul), + * destinatarul si suma din cerinta, nonce-ul = sha256(hash-ul intrarii), un termen marginit, digestul recalculat local, si jumatatea + * serviciului recuperata la semnatarul de politica declarat. + */ +export function completeazaCosemnarea({ payload, req, status, entryHash, agentEvmKey, cosign, now = Math.floor(Date.now() / 1000) }) { + const ethers = incarcaEthers(); + const a = payload && payload.payload && payload.payload.authorization; + if (!a) return { ok: false, error: 'the wallet returned no authorization' }; + const eq = (x, y) => String(x).toLowerCase() === String(y).toLowerCase(); + if (!eq(a.from, status.address)) return { ok: false, error: 'the authorization is not from the 2-of-2 wallet' }; + if (!eq(a.to, req.payTo) || String(a.value) !== String(req.amount)) return { ok: false, error: 'the authorization pays another recipient or amount than the purchase' }; + if (!eq(a.nonce, nonceForEntry(entryHash))) return { ok: false, error: "the authorization nonce is not the agent's ledger entry" }; + if (!(Number(a.validBefore) > now && Number(a.validBefore) <= now + Number(req.maxTimeoutSeconds) + 60)) return { ok: false, error: 'the authorization validity is not the requirement timeout' }; + const domeniu = { name: (req.extra && req.extra.name) || status.tokenName, version: (req.extra && req.extra.version) || status.tokenVersion, + chainId: Number(String(req.network).split(':')[1]), verifyingContract: ethers.getAddress(req.asset) }; + const digest = ethers.TypedDataEncoder.hash(domeniu, EIP3009_TYPES, { ...a, value: BigInt(a.value), validAfter: BigInt(a.validAfter), validBefore: BigInt(a.validBefore) }); + if (!cosign || digest !== cosign.digest) return { ok: false, error: 'the digest the wallet signed is not the one the agent computes' }; + let semnatarPolitica = null; try { semnatarPolitica = ethers.recoverAddress(digest, cosign.policySignature); } catch { semnatarPolitica = null; } + if (!semnatarPolitica || !eq(semnatarPolitica, status.policySigner)) return { ok: false, error: 'the policy half is not signed by the declared policy signer' }; + const agent = typeof agentEvmKey === 'string' ? new ethers.Wallet(agentEvmKey) : agentEvmKey; + const signature = ethers.concat([agent.signingKey.sign(digest).serialized, cosign.policySignature]); + const complet = { ...payload, payload: { ...payload.payload, signature } }; + return { ok: true, header: Buffer.from(JSON.stringify(complet), 'utf8').toString('base64'), paymentPayload: complet }; +} + /** portofelul prin HTTP (serviciul wallet.mjs serve), cu aceeasi forma ca obiectul din createWallet */ export function walletOverHttp(baseUrl, fetchImpl = fetch) { const b = String(baseUrl).replace(/\/+$/, ''); @@ -22,10 +49,11 @@ export function walletOverHttp(baseUrl, fetchImpl = fetch) { * @param {object} o.ledger registrul agentului (openLedger / resumeLedger) * @param {object} o.wallet portofelul (createWallet sau walletOverHttp) * @param {Array} [o.approvals] aprobari umane pentru aceasta plata, cand politica le cere + * @param {string|object} [o.agentEvmKey] cheia secp256k1 a agentului, ceruta numai de un portofel-contract 2-din-2 (modul cosign) * @param {Function} [o.fetchImpl] * @returns {Promise<{paid:boolean, status:number, reason?:string, body?:string, settlement?:object, entry?:object, receipt?:object, decision?:object}>} */ -export async function payWithAgent({ url, ledger, wallet, approvals = [], fetchImpl = fetch }) { +export async function payWithAgent({ url, ledger, wallet, approvals = [], agentEvmKey = null, fetchImpl = fetch }) { const r0 = await fetchImpl(url); if (r0.status !== 402) return { paid: false, status: r0.status, body: await r0.text(), reason: r0.ok ? 'no payment was required' : `the resource answered ${r0.status}` }; const pr = dinB64json(r0.headers.get('payment-required')) || (await r0.json().catch(() => null)); @@ -39,8 +67,16 @@ export async function payWithAgent({ url, ledger, wallet, approvals = [], fetchI // 2. semnatura portofelului, care judeca din nou tot registrul const a = await wallet.authorize({ requirements: req, resource: pr.resource || null, ledger: ledger.export() }); if (!a.ok) return { paid: false, status: 402, reason: `the wallet refused: ${a.error}`, entry: r.entry, equivocation: a.equivocation }; + // 2b. portofelul-contract 2-din-2: agentul verifica si isi adauga jumatatea + let header = a.header; + if (a.cosign) { + if (!agentEvmKey) return { paid: false, status: 402, reason: "the wallet is a 2-of-2 contract and needs the agent's signature: agentEvmKey is missing", entry: r.entry }; + const c = completeazaCosemnarea({ payload: a.paymentPayload, req, status: w, entryHash: r.entry.hash, agentEvmKey, cosign: a.cosign }); + if (!c.ok) return { paid: false, status: 402, reason: `the agent refused to co-sign: ${c.error}`, entry: r.entry }; + header = c.header; + } // 3. cererea reluata, cu plata - const r1 = await fetchImpl(url, { headers: { 'PAYMENT-SIGNATURE': a.header } }); + const r1 = await fetchImpl(url, { headers: { 'PAYMENT-SIGNATURE': header } }); const settlement = dinB64json(r1.headers.get('payment-response')); const body = await r1.text(); return { paid: r1.ok && !!(settlement && settlement.success), status: r1.status, body, settlement, entry: r.entry, receipt: a.receipt, decision: a.decision, diff --git a/agents/x402/contract-2of2.mjs b/agents/x402/contract-2of2.mjs new file mode 100644 index 0000000..48979c3 --- /dev/null +++ b/agents/x402/contract-2of2.mjs @@ -0,0 +1,37 @@ +// Portofelul-contract 2-din-2 al agentului (AereAgentWallet2of2.sol), vazut din afara (2026-09-29): artefactul compilat si codul de +// rulare ASTEPTAT pentru o pereche de semnatari. Un contract care doar RASPUNDE agentSigner()/policySigner() ar putea minti; octetii de +// pe lant nu pot: codul de rulare e cel compilat din sursa publicata, cu cele doua adrese scrise in locul imutabilelor, si nimic altceva. +import fs from 'node:fs'; +import path from 'node:path'; +import crypto from 'node:crypto'; +import { fileURLToPath } from 'node:url'; + +const AICI = path.dirname(fileURLToPath(import.meta.url)); +export const ARTEFACT = path.join(AICI, 'AereAgentWallet2of2.json'); + +export function incarcaArtefact(f = ARTEFACT) { + const a = JSON.parse(fs.readFileSync(f, 'utf8')); + if (a.kind !== 'aere-agent-wallet-2of2-artifact' || !a.deployedBytecode || !a.immutables || !a.immutables.agentSigner || !a.immutables.policySigner) + throw new Error('not an AereAgentWallet2of2 artifact'); + return a; +} + +/** sha256 al sursei contractului, citit din intrarea compilatorului (ce s-a compilat), nu dintr-un camp declarat */ +export function amprentaSursei(a) { + const s = a.standardJsonInput && a.standardJsonInput.sources && a.standardJsonInput.sources[a.sourcePath]; + return s ? crypto.createHash('sha256').update(s.content, 'utf8').digest('hex') : null; +} + +/** codul de rulare pe care il are pe lant un AereAgentWallet2of2(agentSigner, policySigner), in hex cu litere mici */ +export function codulAsteptat(a, { agentSigner, policySigner }) { + const cod = Buffer.from(a.deployedBytecode.replace(/^0x/, ''), 'hex'); + const pune = (poz, adresa) => { + const h = String(adresa).toLowerCase().replace(/^0x/, ''); + if (!/^[0-9a-f]{40}$/.test(h)) throw new Error(`not an address: ${adresa}`); + const cuvant = Buffer.from(h.padStart(64, '0'), 'hex'); + for (const p of poz) { if (p.length !== 32) throw new Error('immutable of unexpected length'); cuvant.copy(cod, p.start); } + }; + pune(a.immutables.agentSigner, agentSigner); + pune(a.immutables.policySigner, policySigner); + return '0x' + cod.toString('hex'); +} diff --git a/agents/x402/contract/AereAgentWallet2of2.sol b/agents/x402/contract/AereAgentWallet2of2.sol new file mode 100644 index 0000000..957a96c --- /dev/null +++ b/agents/x402/contract/AereAgentWallet2of2.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; + +/** + * @title AereAgentWallet2of2, an AI agent's payment wallet that neither the agent nor its owner can spend from alone + * + * @notice The wallet holds tokens and has no function that moves them. Tokens leave it only through a token that asks it, + * by ERC-1271, whether a signature over a digest is valid (EIP-3009 transferWithAuthorization in the testnet token + * AereTestUSD3009 does that for a contract `from`). The answer is yes only for a 130-byte signature that is the agent's + * secp256k1 signature followed by the policy signer's, both over that digest, both non-malleable (OpenZeppelin ECDSA). + * + * The agent holds the first key. The owner's policy service holds the second and co-signs a payment only when the + * agent's post-quantum ledger records it and the owner's policy allows it (tools/agent-policy/x402/wallet.mjs, the + * `cosign` mode). So a stolen agent key cannot spend past the policy, and the owner cannot spend without the agent. + * + * HONEST SCOPE. Both signers are fixed at deployment; there is no rotation, no recovery and no owner: if either key + * is lost, the tokens stay in the wallet. The agent's key here is classical (secp256k1); the post-quantum binding is + * the ledger the policy service checks before it co-signs, not this contract. Any digest both keys sign is valid, as + * in any 2-of-2 multisig: the software signs only EIP-3009 digests. Written 2026-09-29 for the public testnet 28001; + * not deployed on chain 2800 (that is the founder's decision). + */ +contract AereAgentWallet2of2 is IERC1271 { + bytes4 private constant MAGIC = 0x1626ba7e; + bytes4 private constant INVALID = 0xffffffff; + + /// @notice the agent's key (first 65 bytes of a valid signature) + address public immutable agentSigner; + /// @notice the owner's policy service key (last 65 bytes of a valid signature) + address public immutable policySigner; + + error ZeroSigner(); + error SameSigner(); + + constructor(address agent, address policy) { + if (agent == address(0) || policy == address(0)) revert ZeroSigner(); + if (agent == policy) revert SameSigner(); + agentSigner = agent; + policySigner = policy; + } + + /// @inheritdoc IERC1271 + function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4) { + if (signature.length != 130) return INVALID; + (address a, ECDSA.RecoverError ea) = ECDSA.tryRecover(hash, signature[0:65]); + if (ea != ECDSA.RecoverError.NoError || a != agentSigner) return INVALID; + (address p, ECDSA.RecoverError ep) = ECDSA.tryRecover(hash, signature[65:130]); + if (ep != ECDSA.RecoverError.NoError || p != policySigner) return INVALID; + return MAGIC; + } +} diff --git a/agents/x402/control-negativ-wallet.mjs b/agents/x402/control-negativ-wallet.mjs index 8af725b..f3769ce 100644 --- a/agents/x402/control-negativ-wallet.mjs +++ b/agents/x402/control-negativ-wallet.mjs @@ -1,4 +1,5 @@ -// Controlul negativ al portofelului de plati x402 (proba-wallet.mjs) si al verificatorului de plati (proba-verifica-plati.mjs): fiecare paznic se strica intr-o COPIE (agent-policy/*.mjs si +// Controlul negativ al portofelului de plati x402 (proba-wallet.mjs), al modului cosign 2-din-2 (proba-cosign.mjs) si al verificatorului de plati +// (proba-verifica-plati.mjs, inclusiv codul portofelului-contract): fiecare paznic se strica intr-o COPIE (agent-policy/*.mjs si // x402/*.mjs, in aceeasi asezare), proba ruleaza pe copie si trebuie sa iasa rosie EXACT pe verificarea numita, cu proba chiar rulata. // Plantarile sunt conditii false la rulare sau randuri scoase, deci copia se incarca intotdeauna; o ancora care nu apare exact o data, // sau o proba care nu ajunge la rezumat, e un esec al controlului (STRICAT), nu o linie informativa. @@ -16,7 +17,7 @@ const SUS = path.resolve(AICI, '..'); let ETHERS = process.env.AERE_ETHERS || null; // o copie a acestui dosar (controlul portii) primeste calea din mediu if (!ETHERS) try { ETHERS = createRequire(path.join(AICI, 'wallet.mjs')).resolve('ethers'); } catch { try { ETHERS = createRequire(path.join(AICI, 'wallet.mjs')).resolve(path.resolve(SUS, '..', '..', 'contracts', 'node_modules', 'ethers')); } catch { console.log('NEMASURAT: ethers nu se gaseste (npm install aici, sau AERE_ETHERS=)'); process.exit(2); } } -const V = 'proba-verifica-plati.mjs'; +const V = 'proba-verifica-plati.mjs', K = 'proba-cosign.mjs'; const PLANTARI = [ // [nume, fisier (relativ la x402/), tipar, inlocuire, textul verificarii care trebuie sa iasa rosie] ['cererile nu mai sunt judecate una cate una', 'wallet.mjs', 'function authorize(x) { const r = coada.then(() => autorizeaza(x)); coada = r.catch(() => {}); return r; }', 'function authorize(x) { return autorizeaza(x); }', '12. ATAC'], @@ -32,6 +33,13 @@ const PLANTARI = [ ['serverul de resurse primeste o plata pentru alta cerinta', 'resource-server.mjs', 'if (cheie(payload.accepted) !== cheie(requirement)) return cere(', "if (cheie(payload.accepted) !== cheie(requirement) && process.env.AERE_PLANTA_NICIODATA === 'da') return cere(", '11. CONTROL'], ['clientul cere semnatura si cand politica a refuzat', 'client.mjs', "if (!r.allowed) return { paid: false, status: 402, reason: `the agent's policy refused the payment: ${r.reason}`, entry: r.entry };", '', '2. CONTROL'], ['portofelul porneste si fara limita in activul lui', 'wallet.mjs', 'if (!policy.spend || policy.spend.asset !== assetId(network, token)) throw', "if ((!policy.spend || policy.spend.asset !== assetId(network, token)) && process.env.AERE_PLANTA_NICIODATA === 'da') throw", 'o politica fara limita'], + // modul cosign (portofel-contract 2-din-2): ce verifica agentul inainte sa-si adauge jumatatea de semnatura + ['agentul semneaza o autorizare din alt portofel', 'client.mjs', 'if (!eq(a.from, status.address)) return', 'if (false) return', 'schimba platitorul', K], + ['agentul semneaza alt destinatar sau alta suma', 'client.mjs', 'if (!eq(a.to, req.payTo) || String(a.value) !== String(req.amount)) return', 'if (false) return', 'schimba destinatarul', K], + ['agentul semneaza alt nonce decat intrarea lui', 'client.mjs', 'if (!eq(a.nonce, nonceForEntry(entryHash))) return', 'if (false) return', 'alt nonce', K], + ['agentul semneaza un termen oricat de lung', 'client.mjs', 'if (!(Number(a.validBefore) > now && Number(a.validBefore) <= now + Number(req.maxTimeoutSeconds) + 60)) return', 'if (false) return', 'lungeste termenul', K], + ['agentul nu mai compara digestul cu al lui', 'client.mjs', 'if (!cosign || digest !== cosign.digest) return', 'if (!cosign) return', 'alt digest', K], + ['agentul nu mai cere jumatatea semnatarului declarat', 'client.mjs', 'if (!semnatarPolitica || !eq(semnatarPolitica, status.policySigner)) return', 'if (false) return', 'alta cheie decat semnatarul declarat', K], // verificatorul platilor, pe raspunsurile inregistrate de pe 28001 ['verificatorul nu mai cere ca registrul sa verifice', 'verifica-plati.mjs', 'entries)`, v.ok, v.error', 'entries)`, true, v.error', '2. CONTROL: o suma schimbata', V], ['verificatorul nu mai cere ca intrarea platii sa fie in registru', 'verifica-plati.mjs', '!!e && e.hash === p.entryHash)', '!!e)', '3. CONTROL', V], @@ -41,12 +49,17 @@ const PLANTARI = [ ['verificatorul nu mai cere suma din Transfer', 'verifica-plati.mjs', '&& BigInt(l.data) === BigInt(a.amount)));', '));', '7. CONTROL', V], ['verificatorul nu mai cere ca orice Transfer sa fie in registru', 'verifica-plati.mjs', 'straine.length === 0,', 'true,', '8. CONTROL', V], ['verificatorul nu mai cere lantul dosarului', 'verifica-plati.mjs', 'lantul === chain,', 'true,', '9. CONTROL', V], + // portofelul-contract 2-din-2 in dosar: codul de pe lant + ['verificatorul nu mai judeca portofelul-contract', 'verifica-plati.mjs', ' if (d.walletContract) {', " if (d.walletContract && process.env.AERE_PLANTA_NICIODATA === 'da') {", '14. CONTROL', V], + ['verificatorul cere doar lungimea codului, nu codul', 'verifica-plati.mjs', 'cod === asteptat, cod === asteptat ?', 'cod.length === asteptat.length, cod === asteptat ?', '12. CONTROL', V], + ['codul asteptat nu mai poarta semnatarii din dosar', 'contract-2of2.mjs', 'cuvant.copy(cod, p.start); }', '}', '11. dosarul portofelului 2-din-2', V], ]; function copie() { const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-wallet-ctl-')); for (const f of fs.readdirSync(SUS).filter((n) => n.endsWith('.mjs'))) fs.copyFileSync(path.join(SUS, f), path.join(t, f)); fs.mkdirSync(path.join(t, 'x402')); - for (const f of fs.readdirSync(AICI).filter((n) => n.endsWith('.mjs'))) fs.copyFileSync(path.join(AICI, f), path.join(t, 'x402', f)); + // modulele si artefactul contractului 2-din-2 (verificatorul il citeste de langa el); nu package*.json si nu node_modules + for (const f of fs.readdirSync(AICI).filter((n) => n.endsWith('.mjs') || n === 'AereAgentWallet2of2.json')) fs.copyFileSync(path.join(AICI, f), path.join(t, 'x402', f)); fs.cpSync(path.join(AICI, 'dovezi-28001'), path.join(t, 'x402', 'dovezi-28001'), { recursive: true }); return t; } @@ -57,13 +70,13 @@ function ruleaza(t, proba = 'proba-wallet.mjs') { const c = spawn(process.execPath, [path.join(t, 'x402', proba)], { env: { ...process.env, AERE_ETHERS: ETHERS } }); let out = ''; const ceas = setTimeout(() => c.kill(), 240000); c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; }); - c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /(agent-wallet|verifica-plati): \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => /^\s+RAU\s/.test(l)) }); }); + c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /(agent-wallet|verifica-plati|agent-cosign): \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => /^\s+RAU\s/.test(l)) }); }); }); } async function inGrup(lucrari) { const rez = new Array(lucrari.length); let i = 0; await Promise.all(Array.from({ length: PARALEL }, async () => { while (i < lucrari.length) { const k = i++; rez[k] = await lucrari[k](); } })); return rez; } let esecuri = 0; -const martori = await inGrup(['proba-wallet.mjs', V].map((proba) => async () => { const t0 = copie(); try { return [proba, await ruleaza(t0, proba)]; } finally { fs.rmSync(t0, { recursive: true, force: true }); } })); +const martori = await inGrup(['proba-wallet.mjs', V, K].map((proba) => async () => { const t0 = copie(); try { return [proba, await ruleaza(t0, proba)]; } finally { fs.rmSync(t0, { recursive: true, force: true }); } })); for (const [proba, m0] of martori) { if (m0.cod === 0 && m0.rulat && !m0.rosii.length) console.log(` OK martorul ${proba}: copia neatinsa verde`); else { esecuri++; console.log(` RAU martorul ${proba} nu e verde (cod ${m0.cod}, ${m0.rulat ? m0.rosii.length + ' RAU' : 'nu a ajuns la rezumat'})`); } diff --git a/agents/x402/dovezi-28001/plati-agent-2026-09-29-21-42-10.json b/agents/x402/dovezi-28001/plati-agent-2026-09-29-21-42-10.json new file mode 100644 index 0000000..9543b1f --- /dev/null +++ b/agents/x402/dovezi-28001/plati-agent-2026-09-29-21-42-10.json @@ -0,0 +1,164 @@ +{ + "v": 1, + "kind": "aere-agent-x402-payments", + "network": "eip155:28001", + "token": "0x8215bA247a3574af8EBC36606eB437811E318FBd", + "wallet": "0xD07ff8B519EDf9c2EbecFe230602fb4598334a37", + "fromBlock": 4030703, + "facilitator": "https://testnet-rpc.aere.network/x402", + "policy": { + "v": 1, + "kind": "aere-agent-policy", + "agentId": "aere-agent:52e7dea6745a533c34eb58c23eae115eb7e752fb", + "spend": { + "amount": "30000", + "windowSeconds": 3600, + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd" + }, + "tools": null, + "recipients": [ + "0x7bdf94f6de68720a494917fec1114745da71cb9e" + ], + "wallet": "0xd07ff8b519edf9c2ebecfe230602fb4598334a37" + }, + "policyHash": "0xed6e1a16ad1fe3f48640be27480fd4a553f71f1240a520e78251ad5b8627dc69", + "ledger": { + "version": "aere-agent-ledger/2 (2026-09-29)", + "agentId": "aere-agent:52e7dea6745a533c34eb58c23eae115eb7e752fb", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n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\ntgab72wT\n-----END PUBLIC KEY-----\n", + "policyHash": "0xed6e1a16ad1fe3f48640be27480fd4a553f71f1240a520e78251ad5b8627dc69", + "session": "dac452b8757cf2df425001a198fbaf38", + "entries": [ + { + "seq": 0, + "prev": "0000000000000000000000000000000000000000000000000000000000000000", + "body": { + "v": 2, + "agentId": "aere-agent:52e7dea6745a533c34eb58c23eae115eb7e752fb", + "policyHash": "0xed6e1a16ad1fe3f48640be27480fd4a553f71f1240a520e78251ad5b8627dc69", + "session": "dac452b8757cf2df425001a198fbaf38", + "at": 1790718113, + "action": { + "kind": "payment", + "from": "0xd07ff8b519edf9c2ebecfe230602fb4598334a37", + "to": "0x7bdf94f6de68720a494917fec1114745da71cb9e", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x76ea9efa9a280992b196159c34a66078498144010d04fc573ea5aa7c072efe5b", + "at": 1790718113 + }, + "decision": { + "allowed": true, + "reason": "under the limit" + }, + "provenance": null, + "seq": 0 + }, + "signature": "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", + "hash": "bbcc4949c1c561ad02699577f4c2755d90f06b737b0a08bf15caad3e9b3998b4" + }, + { + "seq": 1, + "prev": "bbcc4949c1c561ad02699577f4c2755d90f06b737b0a08bf15caad3e9b3998b4", + "body": { + "v": 2, + "agentId": "aere-agent:52e7dea6745a533c34eb58c23eae115eb7e752fb", + "policyHash": "0xed6e1a16ad1fe3f48640be27480fd4a553f71f1240a520e78251ad5b8627dc69", + "session": "dac452b8757cf2df425001a198fbaf38", + "at": 1790718118, + "action": { + "kind": "payment", + "from": "0xd07ff8b519edf9c2ebecfe230602fb4598334a37", + "to": "0x7bdf94f6de68720a494917fec1114745da71cb9e", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x76ea9efa9a280992b196159c34a66078498144010d04fc573ea5aa7c072efe5b", + "at": 1790718118 + }, + "decision": { + "allowed": true, + "reason": "under the limit" + }, + "provenance": null, + "seq": 1 + }, + "signature": "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", + "hash": "0fa5efcd15c432ef8326839f0c796c6a6e98cbec371f5c40460326845719ac3a" + }, + { + "seq": 2, + "prev": "0fa5efcd15c432ef8326839f0c796c6a6e98cbec371f5c40460326845719ac3a", + "body": { + "v": 2, + "agentId": "aere-agent:52e7dea6745a533c34eb58c23eae115eb7e752fb", + "policyHash": "0xed6e1a16ad1fe3f48640be27480fd4a553f71f1240a520e78251ad5b8627dc69", + "session": "dac452b8757cf2df425001a198fbaf38", + "at": 1790718122, + "action": { + "kind": "payment", + "from": "0xd07ff8b519edf9c2ebecfe230602fb4598334a37", + "to": "0x7bdf94f6de68720a494917fec1114745da71cb9e", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x76ea9efa9a280992b196159c34a66078498144010d04fc573ea5aa7c072efe5b", + "at": 1790718122 + }, + "decision": { + "allowed": true, + "reason": "under the limit" + }, + "provenance": null, + "seq": 2 + }, + "signature": "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", + "hash": "2bf1b50bf7e833f2d49cc9c14ae7c64abcc033da7b59daa440bbd398ff49f159" + }, + { + "seq": 3, + "prev": "2bf1b50bf7e833f2d49cc9c14ae7c64abcc033da7b59daa440bbd398ff49f159", + "body": { + "v": 2, + "agentId": "aere-agent:52e7dea6745a533c34eb58c23eae115eb7e752fb", + "policyHash": "0xed6e1a16ad1fe3f48640be27480fd4a553f71f1240a520e78251ad5b8627dc69", + "session": "dac452b8757cf2df425001a198fbaf38", + "at": 1790718127, + "action": { + "kind": "payment", + "from": "0xd07ff8b519edf9c2ebecfe230602fb4598334a37", + "to": "0x7bdf94f6de68720a494917fec1114745da71cb9e", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x76ea9efa9a280992b196159c34a66078498144010d04fc573ea5aa7c072efe5b", + "at": 1790718127 + }, + "decision": { + "allowed": false, + "reason": "over the limit: 40000 > 30000 per 3600s" + }, + "provenance": null, + "seq": 3 + }, + "signature": "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", + "hash": "dab777eecfa1ee52eda69e9f1e23880e4ac6b1df8dcb23636ea149bc0461273e" + } + ] + }, + "payments": [ + { + "entrySeq": 0, + "entryHash": "bbcc4949c1c561ad02699577f4c2755d90f06b737b0a08bf15caad3e9b3998b4", + "transaction": "0xd00d81dbeac63874f11146b10008eb614a0c8c550176ed1e46a5989de41f2d20" + }, + { + "entrySeq": 1, + "entryHash": "0fa5efcd15c432ef8326839f0c796c6a6e98cbec371f5c40460326845719ac3a", + "transaction": "0xdc654e77fb993a7434b1146fdb94a45ad592eaa42e7099bf0df7db1eae3271ec" + }, + { + "entrySeq": 2, + "entryHash": "2bf1b50bf7e833f2d49cc9c14ae7c64abcc033da7b59daa440bbd398ff49f159", + "transaction": "0xe01fe7e8cf63237a2fc1fd58a416c84ce051bb372a806bcf228f843f87699a55" + } + ], + "createdAt": "2026-09-29T21:42:10.588Z" +} diff --git a/agents/x402/dovezi-28001/plati-agent-2of2-2026-09-29-21-29-48.json b/agents/x402/dovezi-28001/plati-agent-2of2-2026-09-29-21-29-48.json new file mode 100644 index 0000000..6de45b1 --- /dev/null +++ b/agents/x402/dovezi-28001/plati-agent-2of2-2026-09-29-21-29-48.json @@ -0,0 +1,171 @@ +{ + "v": 1, + "kind": "aere-agent-x402-payments", + "network": "eip155:28001", + "token": "0x8215bA247a3574af8EBC36606eB437811E318FBd", + "wallet": "0xB148fE63BFaf760A3B741CE0B68ef1B9a04A7826", + "fromBlock": 4029318, + "walletContract": { + "contract": "AereAgentWallet2of2", + "sourceSha256": "dcdc02a7bd5b94b203a4aa889a4fac10d06f4aa24fe8e6c88d2a1ecfd582de03", + "agentSigner": "0x9c32bAB9c0d9c992166f8C0994770806562c082E", + "policySigner": "0x0852C8FC71Ec6F632Af628442A0efA1294612Ce5", + "deployTransaction": "0x650303ec976706e6d55cc18b1440c94bcb83257f4b7100bcfe0d641a87fd5c65" + }, + "facilitator": "https://testnet-rpc.aere.network/x402", + "policy": { + "v": 1, + "kind": "aere-agent-policy", + "agentId": "aere-agent:9a3c54ce2ab65619fa51003ae602805104f6a208", + "spend": { + "amount": "30000", + "windowSeconds": 3600, + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd" + }, + "tools": null, + "recipients": [ + "0x388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca" + ], + "wallet": "0xb148fe63bfaf760a3b741ce0b68ef1b9a04a7826" + }, + "policyHash": "0x87ebb73fd622d5ab78e4f7b021f037322b104c18db53949d0e07d147d2b65dd4", + "ledger": { + "version": "aere-agent-ledger/2 (2026-09-29)", + "agentId": "aere-agent:9a3c54ce2ab65619fa51003ae602805104f6a208", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n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\nkecWJpYj\n-----END PUBLIC KEY-----\n", + "policyHash": "0x87ebb73fd622d5ab78e4f7b021f037322b104c18db53949d0e07d147d2b65dd4", + "session": "a60c6ae307a33cc1f395669f85d1c0db", + "entries": [ + { + "seq": 0, + "prev": "0000000000000000000000000000000000000000000000000000000000000000", + "body": { + "v": 2, + "agentId": "aere-agent:9a3c54ce2ab65619fa51003ae602805104f6a208", + "policyHash": "0x87ebb73fd622d5ab78e4f7b021f037322b104c18db53949d0e07d147d2b65dd4", + "session": "a60c6ae307a33cc1f395669f85d1c0db", + "at": 1790717361, + "action": { + "kind": "payment", + "from": "0xb148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "to": "0x388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x03f3dc7265c2210686dcc7dc947c1c0e59f3d18b86099c0f195b1ed96ea8f3c5", + "at": 1790717361 + }, + "decision": { + "allowed": true, + "reason": "under the limit" + }, + "provenance": null, + "seq": 0 + }, + "signature": "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", + "hash": "bb0d70cead599bac06f27a989a20b5845adcb405ae6e7f4db93fe638c432643b" + }, + { + "seq": 1, + "prev": "bb0d70cead599bac06f27a989a20b5845adcb405ae6e7f4db93fe638c432643b", + "body": { + "v": 2, + "agentId": "aere-agent:9a3c54ce2ab65619fa51003ae602805104f6a208", + "policyHash": "0x87ebb73fd622d5ab78e4f7b021f037322b104c18db53949d0e07d147d2b65dd4", + "session": "a60c6ae307a33cc1f395669f85d1c0db", + "at": 1790717368, + "action": { + "kind": "payment", + "from": "0xb148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "to": "0x388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x03f3dc7265c2210686dcc7dc947c1c0e59f3d18b86099c0f195b1ed96ea8f3c5", + "at": 1790717368 + }, + "decision": { + "allowed": true, + "reason": "under the limit" + }, + "provenance": null, + "seq": 1 + }, + "signature": "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", + "hash": "6c18e4a83daace82588fbdf2570dd0351ea0947cd002625c0955a82bfca35b51" + }, + { + "seq": 2, + "prev": "6c18e4a83daace82588fbdf2570dd0351ea0947cd002625c0955a82bfca35b51", + "body": { + "v": 2, + "agentId": "aere-agent:9a3c54ce2ab65619fa51003ae602805104f6a208", + "policyHash": "0x87ebb73fd622d5ab78e4f7b021f037322b104c18db53949d0e07d147d2b65dd4", + "session": "a60c6ae307a33cc1f395669f85d1c0db", + "at": 1790717374, + "action": { + "kind": "payment", + "from": "0xb148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "to": "0x388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x03f3dc7265c2210686dcc7dc947c1c0e59f3d18b86099c0f195b1ed96ea8f3c5", + "at": 1790717374 + }, + "decision": { + "allowed": true, + "reason": "under the limit" + }, + "provenance": null, + "seq": 2 + }, + "signature": "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", + "hash": "fc918dd42c240c4d25c1652d40dc1dbd8ec6f4c1d12f4f32f90c31a8848e469f" + }, + { + "seq": 3, + "prev": "fc918dd42c240c4d25c1652d40dc1dbd8ec6f4c1d12f4f32f90c31a8848e469f", + "body": { + "v": 2, + "agentId": "aere-agent:9a3c54ce2ab65619fa51003ae602805104f6a208", + "policyHash": "0x87ebb73fd622d5ab78e4f7b021f037322b104c18db53949d0e07d147d2b65dd4", + "session": "a60c6ae307a33cc1f395669f85d1c0db", + "at": 1790717379, + "action": { + "kind": "payment", + "from": "0xb148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "to": "0x388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca", + "amount": "10000", + "asset": "eip155:28001/erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd", + "ref": "0x03f3dc7265c2210686dcc7dc947c1c0e59f3d18b86099c0f195b1ed96ea8f3c5", + "at": 1790717379 + }, + "decision": { + "allowed": false, + "reason": "over the limit: 40000 > 30000 per 3600s" + }, + "provenance": null, + "seq": 3 + }, + "signature": "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", + "hash": "36fc236fe803d05f787c8ca31005cda8445c573e4ee8cad05ba889a01cd0ba63" + } + ] + }, + "payments": [ + { + "entrySeq": 0, + "entryHash": "bb0d70cead599bac06f27a989a20b5845adcb405ae6e7f4db93fe638c432643b", + "transaction": "0x5517ca46b81e474b73ff45476d7b70eceec22be963ef734c651d1edd24d041a4" + }, + { + "entrySeq": 1, + "entryHash": "6c18e4a83daace82588fbdf2570dd0351ea0947cd002625c0955a82bfca35b51", + "transaction": "0x8d709c307943baecb9223cb32f3f5f1f3e4f1a6442925d60f1be470a02821675" + }, + { + "entrySeq": 2, + "entryHash": "fc918dd42c240c4d25c1652d40dc1dbd8ec6f4c1d12f4f32f90c31a8848e469f", + "transaction": "0x4c0779db602945d787f2b795a803922b14030da67bfaa689103b85e344dd12ac" + } + ], + "createdAt": "2026-09-29T21:29:48.714Z" +} diff --git a/agents/x402/dovezi-28001/rpc-inregistrat-2026-09-29-21-42-10.json b/agents/x402/dovezi-28001/rpc-inregistrat-2026-09-29-21-42-10.json new file mode 100644 index 0000000..ad83cdb --- /dev/null +++ b/agents/x402/dovezi-28001/rpc-inregistrat-2026-09-29-21-42-10.json @@ -0,0 +1,205 @@ +{ + "v": 1, + "recordedAt": "2026-09-29T21:42:19.343Z", + "rpc": "https://testnet-rpc.aere.network", + "chainId": "0x6d61", + "receipts": { + "0xd00d81dbeac63874f11146b10008eb614a0c8c550176ed1e46a5989de41f2d20": { + "blockHash": "0x2b37241d2181dadfaa03f8a07d84cc268556782a8c73b14435b82b6e0415b6bd", + "blockNumber": "0x3d80f9", + "contractAddress": null, + "cumulativeGasUsed": "0x144b8", + "from": "0x50f2a153be2c248b7050914a08f4f6f4498c4e48", + "gasUsed": "0x144b8", + "effectiveGasPrice": "0x3b9aca00", + "logs": [ + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0x98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a5", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0xa97a7a593a9badd25da8eec3c1cb9c277748c213179ccbbaa15ffc12e916581c" + ], + "data": "0x", + "blockNumber": "0x3d80f9", + "transactionHash": "0xd00d81dbeac63874f11146b10008eb614a0c8c550176ed1e46a5989de41f2d20", + "transactionIndex": "0x0", + "blockHash": "0x2b37241d2181dadfaa03f8a07d84cc268556782a8c73b14435b82b6e0415b6bd", + "blockTimestamp": "0x6abc30a2", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x0000000000000000000000007bdf94f6de68720a494917fec1114745da71cb9e" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "blockNumber": "0x3d80f9", + "transactionHash": "0xd00d81dbeac63874f11146b10008eb614a0c8c550176ed1e46a5989de41f2d20", + "transactionIndex": "0x0", + "blockHash": "0x2b37241d2181dadfaa03f8a07d84cc268556782a8c73b14435b82b6e0415b6bd", + "blockTimestamp": "0x6abc30a2", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000080000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000200000000008000000000000000000008000000000000000000000080000000002000000000000000000080000000000000000000000000000000000000000010000200000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000004000000000000020000000020000000000002000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000001000000000000000000000000000", + "status": "0x1", + "to": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "transactionHash": "0xd00d81dbeac63874f11146b10008eb614a0c8c550176ed1e46a5989de41f2d20", + "transactionIndex": "0x0", + "type": "0x2" + }, + "0xdc654e77fb993a7434b1146fdb94a45ad592eaa42e7099bf0df7db1eae3271ec": { + "blockHash": "0x4b82db2558d02fecb52d54d41c78082505dcaf4d7dd92f65d91d68d568e93da0", + "blockNumber": "0x3d8101", + "contractAddress": null, + "cumulativeGasUsed": "0x101f8", + "from": "0x50f2a153be2c248b7050914a08f4f6f4498c4e48", + "gasUsed": "0x101f8", + "effectiveGasPrice": "0x3b9aca00", + "logs": [ + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0x98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a5", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x8285c4690c836b1196d71a44dbca6859a603e8a66baf993c542c58c5f243c8de" + ], + "data": "0x", + "blockNumber": "0x3d8101", + "transactionHash": "0xdc654e77fb993a7434b1146fdb94a45ad592eaa42e7099bf0df7db1eae3271ec", + "transactionIndex": "0x0", + "blockHash": "0x4b82db2558d02fecb52d54d41c78082505dcaf4d7dd92f65d91d68d568e93da0", + "blockTimestamp": "0x6abc30a6", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x0000000000000000000000007bdf94f6de68720a494917fec1114745da71cb9e" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "blockNumber": "0x3d8101", + "transactionHash": "0xdc654e77fb993a7434b1146fdb94a45ad592eaa42e7099bf0df7db1eae3271ec", + "transactionIndex": "0x0", + "blockHash": "0x4b82db2558d02fecb52d54d41c78082505dcaf4d7dd92f65d91d68d568e93da0", + "blockTimestamp": "0x6abc30a6", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000080000000000000000000000100000000000000000000000000000000000000200000000000000000000000000000000000000200000000008000000000200000000008000000000000000000000080000000002000000000000000000080000000000000000000000000000000000000000010000000000000000000000000000000000000000004000000000000004000000000000000000000000000000000000000000000000004000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000001000000000000000000000000000", + "status": "0x1", + "to": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "transactionHash": "0xdc654e77fb993a7434b1146fdb94a45ad592eaa42e7099bf0df7db1eae3271ec", + "transactionIndex": "0x0", + "type": "0x2" + }, + "0xe01fe7e8cf63237a2fc1fd58a416c84ce051bb372a806bcf228f843f87699a55": { + "blockHash": "0x53e86d6e001807b5b02bde5c3bf1378e9a724035e63779390a4af904aaedaf8b", + "blockNumber": "0x3d810a", + "contractAddress": null, + "cumulativeGasUsed": "0x101f8", + "from": "0x50f2a153be2c248b7050914a08f4f6f4498c4e48", + "gasUsed": "0x101f8", + "effectiveGasPrice": "0x3b9aca00", + "logs": [ + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0x98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a5", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x6eb5c75028a636c1fa1cde1bb84b1bd4552b0ddebf254f75cbfd9b0279ce91a6" + ], + "data": "0x", + "blockNumber": "0x3d810a", + "transactionHash": "0xe01fe7e8cf63237a2fc1fd58a416c84ce051bb372a806bcf228f843f87699a55", + "transactionIndex": "0x0", + "blockHash": "0x53e86d6e001807b5b02bde5c3bf1378e9a724035e63779390a4af904aaedaf8b", + "blockTimestamp": "0x6abc30ab", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x0000000000000000000000007bdf94f6de68720a494917fec1114745da71cb9e" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "blockNumber": "0x3d810a", + "transactionHash": "0xe01fe7e8cf63237a2fc1fd58a416c84ce051bb372a806bcf228f843f87699a55", + "transactionIndex": "0x0", + "blockHash": "0x53e86d6e001807b5b02bde5c3bf1378e9a724035e63779390a4af904aaedaf8b", + "blockTimestamp": "0x6abc30ab", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000080000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000200000000008000000000000000000008000000000000000000000080000000002000000000000001000080000000000000000000000000000000000000000010000000000000000000000000000000000000008000000000000000004020000000000000000000000000000000000000000000000004000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000001000000000000000000000000000", + "status": "0x1", + "to": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "transactionHash": "0xe01fe7e8cf63237a2fc1fd58a416c84ce051bb372a806bcf228f843f87699a55", + "transactionIndex": "0x0", + "type": "0x2" + } + }, + "transfersOut": [ + { + "logIndex": "0x1", + "removed": false, + "blockNumber": "0x3d80f9", + "blockHash": "0x2b37241d2181dadfaa03f8a07d84cc268556782a8c73b14435b82b6e0415b6bd", + "blockTimestamp": "0x6abc30a2", + "transactionHash": "0xd00d81dbeac63874f11146b10008eb614a0c8c550176ed1e46a5989de41f2d20", + "transactionIndex": "0x0", + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x0000000000000000000000007bdf94f6de68720a494917fec1114745da71cb9e" + ] + }, + { + "logIndex": "0x1", + "removed": false, + "blockNumber": "0x3d8101", + "blockHash": "0x4b82db2558d02fecb52d54d41c78082505dcaf4d7dd92f65d91d68d568e93da0", + "blockTimestamp": "0x6abc30a6", + "transactionHash": "0xdc654e77fb993a7434b1146fdb94a45ad592eaa42e7099bf0df7db1eae3271ec", + "transactionIndex": "0x0", + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x0000000000000000000000007bdf94f6de68720a494917fec1114745da71cb9e" + ] + }, + { + "logIndex": "0x1", + "removed": false, + "blockNumber": "0x3d810a", + "blockHash": "0x53e86d6e001807b5b02bde5c3bf1378e9a724035e63779390a4af904aaedaf8b", + "blockTimestamp": "0x6abc30ab", + "transactionHash": "0xe01fe7e8cf63237a2fc1fd58a416c84ce051bb372a806bcf228f843f87699a55", + "transactionIndex": "0x0", + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d07ff8b519edf9c2ebecfe230602fb4598334a37", + "0x0000000000000000000000007bdf94f6de68720a494917fec1114745da71cb9e" + ] + } + ] +} diff --git a/agents/x402/dovezi-28001/rpc-inregistrat-2of2-2026-09-29-21-29-48.json b/agents/x402/dovezi-28001/rpc-inregistrat-2of2-2026-09-29-21-29-48.json new file mode 100644 index 0000000..44f2082 --- /dev/null +++ b/agents/x402/dovezi-28001/rpc-inregistrat-2of2-2026-09-29-21-29-48.json @@ -0,0 +1,206 @@ +{ + "v": 1, + "recordedAt": "2026-09-29T21:29:57.375Z", + "rpc": "https://testnet-rpc.aere.network", + "chainId": "0x6d61", + "receipts": { + "0x5517ca46b81e474b73ff45476d7b70eceec22be963ef734c651d1edd24d041a4": { + "blockHash": "0x284f0e5cc02f6d1caa6accb3e03fc33e7af9ac71fd9d0e72a8ab7ef95c79977a", + "blockNumber": "0x3d7baa", + "contractAddress": null, + "cumulativeGasUsed": "0x169c7", + "from": "0x50f2a153be2c248b7050914a08f4f6f4498c4e48", + "gasUsed": "0x169c7", + "effectiveGasPrice": "0x3b9aca00", + "logs": [ + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0x98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a5", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0xf3355acc270b45363d4394d6368731469a7d80b28a3fa118d93a80dff251310c" + ], + "data": "0x", + "blockNumber": "0x3d7baa", + "transactionHash": "0x5517ca46b81e474b73ff45476d7b70eceec22be963ef734c651d1edd24d041a4", + "transactionIndex": "0x0", + "blockHash": "0x284f0e5cc02f6d1caa6accb3e03fc33e7af9ac71fd9d0e72a8ab7ef95c79977a", + "blockTimestamp": "0x6abc2db4", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0x000000000000000000000000388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "blockNumber": "0x3d7baa", + "transactionHash": "0x5517ca46b81e474b73ff45476d7b70eceec22be963ef734c651d1edd24d041a4", + "transactionIndex": "0x0", + "blockHash": "0x284f0e5cc02f6d1caa6accb3e03fc33e7af9ac71fd9d0e72a8ab7ef95c79977a", + "blockTimestamp": "0x6abc2db4", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000200000000000000000040000080000008000000000000000000000080000000000000000000000000000080000000000000002000000000000000000000000010000000000000002000000000000000000000000008000000000000004000000000400000000000000000000000000000000000000004000000000000000000040000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000004000000000", + "status": "0x1", + "to": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "transactionHash": "0x5517ca46b81e474b73ff45476d7b70eceec22be963ef734c651d1edd24d041a4", + "transactionIndex": "0x0", + "type": "0x2" + }, + "0x8d709c307943baecb9223cb32f3f5f1f3e4f1a6442925d60f1be470a02821675": { + "blockHash": "0x998310a4704bad6815bd9e0be054a71fc15b2df7d88f29c12fab26de3bf2261d", + "blockNumber": "0x3d7bb5", + "contractAddress": null, + "cumulativeGasUsed": "0x126ef", + "from": "0x50f2a153be2c248b7050914a08f4f6f4498c4e48", + "gasUsed": "0x126ef", + "effectiveGasPrice": "0x3b9aca00", + "logs": [ + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0x98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a5", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0xdc6d51611ec4debb1fb46620288c02390a84fcbcb6506f4f7c71c31c9b4b71ef" + ], + "data": "0x", + "blockNumber": "0x3d7bb5", + "transactionHash": "0x8d709c307943baecb9223cb32f3f5f1f3e4f1a6442925d60f1be470a02821675", + "transactionIndex": "0x0", + "blockHash": "0x998310a4704bad6815bd9e0be054a71fc15b2df7d88f29c12fab26de3bf2261d", + "blockTimestamp": "0x6abc2dba", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0x000000000000000000000000388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "blockNumber": "0x3d7bb5", + "transactionHash": "0x8d709c307943baecb9223cb32f3f5f1f3e4f1a6442925d60f1be470a02821675", + "transactionIndex": "0x0", + "blockHash": "0x998310a4704bad6815bd9e0be054a71fc15b2df7d88f29c12fab26de3bf2261d", + "blockTimestamp": "0x6abc2dba", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000020000000000000800000000000000000000000000000000000000000000000000000000000200000000000000000000000000000008000000000000000000000080000000000000000000000000000080000000000000000000000000000000000000000010002000000000002000000000000000000000000008000000000000004000000000400000000000000000000000000000000000000004000000000000000000040000100000000002000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000004000000000", + "status": "0x1", + "to": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "transactionHash": "0x8d709c307943baecb9223cb32f3f5f1f3e4f1a6442925d60f1be470a02821675", + "transactionIndex": "0x0", + "type": "0x2" + }, + "0x4c0779db602945d787f2b795a803922b14030da67bfaa689103b85e344dd12ac": { + "blockHash": "0x838535ad20ad0f21a5b0af47bcb50cf9d6f6c5563f5e29ee5e081b9f7372eb6d", + "blockNumber": "0x3d7bbd", + "contractAddress": null, + "cumulativeGasUsed": "0x126fb", + "from": "0x50f2a153be2c248b7050914a08f4f6f4498c4e48", + "gasUsed": "0x126fb", + "effectiveGasPrice": "0x3b9aca00", + "logs": [ + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0x98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a5", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0x3a059e188e3220d176e7d84f6b1c3adecffb595713deb266b2a9f4f9b863ca1c" + ], + "data": "0x", + "blockNumber": "0x3d7bbd", + "transactionHash": "0x4c0779db602945d787f2b795a803922b14030da67bfaa689103b85e344dd12ac", + "transactionIndex": "0x0", + "blockHash": "0x838535ad20ad0f21a5b0af47bcb50cf9d6f6c5563f5e29ee5e081b9f7372eb6d", + "blockTimestamp": "0x6abc2dbf", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0x000000000000000000000000388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "blockNumber": "0x3d7bbd", + "transactionHash": "0x4c0779db602945d787f2b795a803922b14030da67bfaa689103b85e344dd12ac", + "transactionIndex": "0x0", + "blockHash": "0x838535ad20ad0f21a5b0af47bcb50cf9d6f6c5563f5e29ee5e081b9f7372eb6d", + "blockTimestamp": "0x6abc2dbf", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000200000002000000000000000000000008000000000000000000000080000000000000000000000000000080000000000000000000000001000000000000000010000000000000002000000000000000000000000008000000000000004000000000400000000000000000000000000000000000000004000000000000000000040000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000200000000000000004000000000", + "status": "0x1", + "to": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "transactionHash": "0x4c0779db602945d787f2b795a803922b14030da67bfaa689103b85e344dd12ac", + "transactionIndex": "0x0", + "type": "0x2" + } + }, + "transfersOut": [ + { + "logIndex": "0x1", + "removed": false, + "blockNumber": "0x3d7baa", + "blockHash": "0x284f0e5cc02f6d1caa6accb3e03fc33e7af9ac71fd9d0e72a8ab7ef95c79977a", + "blockTimestamp": "0x6abc2db4", + "transactionHash": "0x5517ca46b81e474b73ff45476d7b70eceec22be963ef734c651d1edd24d041a4", + "transactionIndex": "0x0", + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0x000000000000000000000000388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca" + ] + }, + { + "logIndex": "0x1", + "removed": false, + "blockNumber": "0x3d7bb5", + "blockHash": "0x998310a4704bad6815bd9e0be054a71fc15b2df7d88f29c12fab26de3bf2261d", + "blockTimestamp": "0x6abc2dba", + "transactionHash": "0x8d709c307943baecb9223cb32f3f5f1f3e4f1a6442925d60f1be470a02821675", + "transactionIndex": "0x0", + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0x000000000000000000000000388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca" + ] + }, + { + "logIndex": "0x1", + "removed": false, + "blockNumber": "0x3d7bbd", + "blockHash": "0x838535ad20ad0f21a5b0af47bcb50cf9d6f6c5563f5e29ee5e081b9f7372eb6d", + "blockTimestamp": "0x6abc2dbf", + "transactionHash": "0x4c0779db602945d787f2b795a803922b14030da67bfaa689103b85e344dd12ac", + "transactionIndex": "0x0", + "address": "0x8215ba247a3574af8ebc36606eb437811e318fbd", + "data": "0x0000000000000000000000000000000000000000000000000000000000002710", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000b148fe63bfaf760a3b741ce0b68ef1b9a04a7826", + "0x000000000000000000000000388e8ed4eaa0d51ce0e3c228c7514a3e64c434ca" + ] + } + ], + "code": "0x6080604052600436101561001257600080fd5b6000803560e01c80631626ba7e146100ca57806338eab6b9146100855763f4e2592b1461003e57600080fd5b346100825780600319360112610082576040517f0000000000000000000000009c32bab9c0d9c992166f8c0994770806562c082e6001600160a01b03168152602090f35b80fd5b50346100825780600319360112610082576040517f0000000000000000000000000852c8fc71ec6f632af628442a0efa1294612ce56001600160a01b03168152602090f35b5034610082576040366003190112610082576001600160401b03906024359082821161008257366023830112156100825781600401359283116100825736602484840101116100825760206101258460248501600435610193565b6040516001600160e01b03199091168152f35b60405191929190608082016001600160401b0381118382101761017d57604052819360418352604182011161017857816041606192602060009501370152565b600080fd5b634e487b7160e01b600052604160045260246000fd5b90916082810361026b5780604111610178576101b86101b23685610138565b836102b3565b600581101561025557159081159161027b575b5061026b57608211610178576101e86101ee926041369101610138565b906102b3565b600581101561025557159081159161021d575b5061021157630b135d3f60e11b90565b6001600160e01b031990565b7f0000000000000000000000000852c8fc71ec6f632af628442a0efa1294612ce56001600160a01b0390811691161415905038610201565b634e487b7160e01b600052602160045260246000fd5b506001600160e01b031992915050565b7f0000000000000000000000009c32bab9c0d9c992166f8c0994770806562c082e6001600160a01b03908116911614159050386101cb565b9060418151146000146102e1576102dd916020820151906060604084015193015160001a906102eb565b9091565b5050600090600290565b9291906fa2a8918ca85bafe22016d0b997e4df60600160ff1b0383116103645791608094939160ff602094604051948552168484015260408301526060820152600093849182805260015afa156103575781516001600160a01b03811615610351579190565b50600190565b50604051903d90823e3d90fd5b5050505060009060039056fea2646970667358221220c1859b9ad2a87f5981b6e6ade9d1980dabedd70235a69d674774c6603ec6ceac64736f6c63430008170033" +} diff --git a/agents/x402/facilitator-local.mjs b/agents/x402/facilitator-local.mjs index 976d57d..0ab3de1 100644 --- a/agents/x402/facilitator-local.mjs +++ b/agents/x402/facilitator-local.mjs @@ -5,7 +5,9 @@ import http from 'node:http'; import { incarcaEthers, EIP3009_TYPES } from './wallet.mjs'; -export function createLocalFacilitator({ network, token, balances = {} }) { +// `contracts`: portofele-contract 2-din-2 emulate (ERC-1271 ca in AereAgentWallet2of2.sol): { [adresa]: { agentSigner, policySigner } }; +// logica adevarata a contractului o proba hardhat (contracts/test/AereAgentWallet2of2.test.js) si testnetul +export function createLocalFacilitator({ network, token, balances = {}, contracts = {} }) { const ethers = incarcaEthers(); const domeniu = { name: token.name, version: token.version, chainId: Number(network.split(':')[1]), verifyingContract: ethers.getAddress(token.address) }; const sold = new Map(Object.entries(balances).map(([a, v]) => [a.toLowerCase(), BigInt(v)])); @@ -23,8 +25,16 @@ export function createLocalFacilitator({ network, token, balances = {} }) { if (acum <= BigInt(a.validAfter)) return { ok: false, reason: 'authorization_not_yet_valid', payer: a.from }; if (acum + 6n >= BigInt(a.validBefore)) return { ok: false, reason: 'authorization_expired', payer: a.from }; let semnatar = null; - try { semnatar = ethers.verifyTypedData(domeniu, EIP3009_TYPES, { ...a, value: BigInt(a.value), validAfter: BigInt(a.validAfter), validBefore: BigInt(a.validBefore) }, sig); } catch { semnatar = null; } - if (!semnatar || semnatar.toLowerCase() !== String(a.from).toLowerCase()) return { ok: false, reason: 'invalid_signature', payer: a.from }; + const mesaj = { ...a, value: BigInt(a.value), validAfter: BigInt(a.validAfter), validBefore: BigInt(a.validBefore) }; + try { semnatar = ethers.verifyTypedData(domeniu, EIP3009_TYPES, mesaj, sig); } catch { semnatar = null; } + let valid = !!semnatar && semnatar.toLowerCase() === String(a.from).toLowerCase(); + const k = Object.entries(contracts).find(([adr]) => adr.toLowerCase() === String(a.from).toLowerCase()); + if (!valid && k && /^0x[0-9a-fA-F]{260}$/.test(String(sig))) { + const digest = ethers.TypedDataEncoder.hash(domeniu, EIP3009_TYPES, mesaj); + const rec = (h) => { try { return ethers.recoverAddress(digest, h).toLowerCase(); } catch { return null; } }; + valid = rec(ethers.dataSlice(sig, 0, 65)) === k[1].agentSigner.toLowerCase() && rec(ethers.dataSlice(sig, 65, 130)) === k[1].policySigner.toLowerCase(); + } + if (!valid) return { ok: false, reason: 'invalid_signature', payer: a.from }; if (folosite.has(`${a.from.toLowerCase()}:${a.nonce}`)) return { ok: false, reason: 'nonce_already_used', payer: a.from }; if ((sold.get(a.from.toLowerCase()) || 0n) < BigInt(a.value)) return { ok: false, reason: 'insufficient_funds', payer: a.from }; return { ok: true, payer: a.from, a }; diff --git a/agents/x402/inregistreaza-rpc.mjs b/agents/x402/inregistreaza-rpc.mjs index b139ad6..60cd1b2 100644 --- a/agents/x402/inregistreaza-rpc.mjs +++ b/agents/x402/inregistreaza-rpc.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node // inregistreaza-rpc.mjs: scoate de pe lant, O DATA, raspunsurile RPC de care are nevoie verifica-plati.mjs pentru un dosar-dovada -// (eth_chainId, chitanta fiecarei plati, Transfer-urile din portofel), ca proba verificatorului sa ruleze apoi fara retea pe date REALE. +// (eth_chainId, chitanta fiecarei plati, Transfer-urile din portofel, si codul portofelului cand e un contract 2-din-2), ca proba verificatorului sa ruleze apoi fara retea pe date REALE. // node inregistreaza-rpc.mjs --rpc --out import fs from 'node:fs'; import { incarcaEthers } from './wallet.mjs'; @@ -16,5 +16,6 @@ const receipts = {}; for (const p of d.payments) receipts[p.transaction.toLowerCase()] = await apel('eth_getTransactionReceipt', [p.transaction]); const logs = await apel('eth_getLogs', [{ address: d.token, fromBlock: '0x' + Number(d.fromBlock).toString(16), toBlock: 'latest', topics: [ethers.id('Transfer(address,address,uint256)'), '0x' + '0'.repeat(24) + d.wallet.toLowerCase().slice(2)] }]); -fs.writeFileSync(out, JSON.stringify({ v: 1, recordedAt: new Date().toISOString(), rpc, chainId, receipts, transfersOut: logs }, null, 1) + '\n'); -console.log(`recorded chain ${Number(chainId)}: ${Object.keys(receipts).length} receipts, ${logs.length} transfers out of the wallet -> ${out}`); +const code = d.walletContract ? await apel('eth_getCode', [d.wallet, 'latest']) : undefined; +fs.writeFileSync(out, JSON.stringify({ v: 1, recordedAt: new Date().toISOString(), rpc, chainId, receipts, transfersOut: logs, ...(code ? { code } : {}) }, null, 1) + '\n'); +console.log(`recorded chain ${Number(chainId)}: ${Object.keys(receipts).length} receipts, ${logs.length} transfers out of the wallet${code ? `, the wallet code (${(code.length - 2) / 2} bytes)` : ''} -> ${out}`); diff --git a/agents/x402/proba-cosign-testnet.mjs b/agents/x402/proba-cosign-testnet.mjs new file mode 100644 index 0000000..1a781eb --- /dev/null +++ b/agents/x402/proba-cosign-testnet.mjs @@ -0,0 +1,145 @@ +#!/usr/bin/env node +// Proba cap la cap a portofelului-contract 2-din-2 pe testnetul PUBLIC 28001 (2026-09-29, punctele 23, 25): banii agentului stau intr-un +// AereAgentWallet2of2 desfasurat aici, si ies numai cu semnatura agentului SI a serviciului de politica, prin facilitatorul x402 al +// testnetului (ERC-1271) si prin tokenul EIP-3009 (SignatureChecker). +// 1. contractul se desfasoara din artefactul publicat (AereAgentWallet2of2.json), cu doua chei NOI tinute numai in memorie (agentul, +// serviciul de politica); codul de pe lant trebuie sa fie EXACT codul compilat cu cei doi semnatari; +// 2. primeste 0,05 tUSD de la cheia de dezvoltator a testnetului; +// 3. contractul, masurat pe lant (eth_call isValidSignature): DA numai pentru agent || politica; NU pentru fiecare jumatate singura, +// dublata, in ordine inversa, sau cu o cheie straina; +// 4. trei cumparaturi de 0,01 platite din contract si servite; a patra refuzata de politica; +// 5. ATACURI prin facilitatorul testnetului: agentul singur si serviciul de politica singur nu pot plati (402); si TOKENUL insusi, +// intrebat pe lant fara facilitator (eth_call transferWithAuthorization): refuza jumatatea agentului, primeste perechea; +// 6. pe lant: soldurile, nonce-urile folosite (sha256 al intrarii), iar nonce-ul atacului nefolosit; +// 7. dosarul-dovada (cu walletContract) verificat cu verifica-plati.mjs: VALID, inclusiv codul contractului; un dosar care numeste +// alt semnatar de politica: INVALID. +// Tranzactii trimise de proba: desfasurarea si finantarea (cheia de dezvoltator); decontarile le plateste facilitatorul. Refuza orice +// alt lant decat 28001. Cheia de dezvoltator se citeste din AERE_TESTNET_KEY_FILE (d= sau PRIVATE_KEY=0x) si nu se tipareste. +// AERE_TESTNET_KEY_FILE= node proba-cosign-testnet.mjs [--rpc URL] [--facilitator URL] +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { definePolicy } from '../agent-policy.mjs'; +import { newAgentIdentity, openLedger } from '../agent-ledger.mjs'; +import { createWallet, assetId, nonceForEntry, incarcaEthers, EIP3009_TYPES } from './wallet.mjs'; +import { payWithAgent } from './client.mjs'; +import { createResourceServer } from './resource-server.mjs'; +import { verificaPlati } from './verifica-plati.mjs'; +import { incarcaArtefact, codulAsteptat, amprentaSursei } from './contract-2of2.mjs'; + +const AICI = path.dirname(fileURLToPath(import.meta.url)); +const a = process.argv.slice(2); const get = (f, d) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : d; }; +const RPC = get('--rpc', 'https://testnet-rpc.aere.network'); +const FAC = get('--facilitator', 'https://testnet-rpc.aere.network/x402'); +const NET = 'eip155:28001'; +const TOKEN = { address: '0x8215bA247a3574af8EBC36606eB437811E318FBd', name: 'AereTestUSD', version: '2' }; +const MAGIC = '0x1626ba7e', INVALID = '0xffffffff'; +const ethers = incarcaEthers(); +let ok = 0, rau = 0; +const cer = (c, ce) => { console.log((c ? ' OK ' : ' RAU ') + ce); c ? ok++ : rau++; return c; }; +const taie = (s) => String(s || '').replace(/(0x)?[0-9a-fA-F]{60,}/g, '').slice(0, 160); + +const kf = process.env.AERE_TESTNET_KEY_FILE; +if (!kf || !fs.existsSync(kf)) { console.log('NEMASURAT: AERE_TESTNET_KEY_FILE nu numeste un fisier cu cheia de dezvoltator a testnetului'); process.exit(2); } +const rand = fs.readFileSync(kf, 'utf8').split(/\r?\n/).map((l) => l.trim()).find((l) => /^(d|PRIVATE_KEY)=/.test(l)) || ''; +const hex = rand.replace(/^(d|PRIVATE_KEY)=/, '').replace(/^0x/, '').trim(); +if (!/^[0-9a-fA-F]{1,64}$/.test(hex)) { console.log('NEMASURAT: fisierul cheii nu are un rand d= sau PRIVATE_KEY=0x'); process.exit(2); } +const provider = new ethers.JsonRpcProvider(RPC, undefined, { staticNetwork: false }); +const lant = Number((await provider.getNetwork()).chainId); +if (lant !== 28001) { console.log(`REFUZ: RPC-ul serveste lantul ${lant}; proba ruleaza numai pe testnetul 28001`); process.exit(2); } +const dev = new ethers.Wallet('0x' + hex.padStart(64, '0'), provider); +const art = incarcaArtefact(); +const tUSD = new ethers.Contract(TOKEN.address, ['function transfer(address,uint256) returns (bool)', 'function balanceOf(address) view returns (uint256)', + 'function authorizationState(address,bytes32) view returns (bool)', + 'function transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)'], dev); +const DOM = { name: TOKEN.name, version: TOKEN.version, chainId: 28001, verifyingContract: TOKEN.address }; +const digestul = (au) => ethers.TypedDataEncoder.hash(DOM, EIP3009_TYPES, { ...au, value: BigInt(au.value), validAfter: BigInt(au.validAfter), validBefore: BigInt(au.validBefore) }); +const deschise = []; + +try { + // 1. desfasurarea, din artefactul publicat + const agentEvm = ethers.Wallet.createRandom(), politica = ethers.Wallet.createRandom(), strain = ethers.Wallet.createRandom(); + const payee = ethers.Wallet.createRandom().address; + const c = await new ethers.ContractFactory(art.abi, art.bytecode, dev).deploy(agentEvm.address, politica.address); + const rcD = await c.deploymentTransaction().wait(1, 120000); + const portofel = await c.getAddress(); + const cod = String(await provider.getCode(portofel)).toLowerCase(); + cer(rcD && rcD.status === 1 && cod === codulAsteptat(art, { agentSigner: agentEvm.address, policySigner: politica.address }), + `1. AereAgentWallet2of2 desfasurat la ${portofel} (bloc ${rcD && rcD.blockNumber}): codul de pe lant e exact codul compilat din sursa publicata (sha256 ${String(amprentaSursei(art)).slice(0, 16)}), cu cei doi semnatari`); + // 2. finantarea + const rc0 = await (await tUSD.transfer(portofel, 50000n)).wait(1, 120000); + cer(rc0 && rc0.status === 1 && (await tUSD.balanceOf(portofel)) === 50000n, `2. portofelul-contract primeste 0,05 tUSD (bloc ${rc0 && rc0.blockNumber})`); + // 3. contractul, pe lant + const w = new ethers.Contract(portofel, art.abi, provider); + const h = ethers.hexlify(ethers.randomBytes(32)); + const sA = agentEvm.signingKey.sign(h).serialized, sP = politica.signingKey.sign(h).serialized, sS = strain.signingKey.sign(h).serialized; + const raspunsuri = { + 'agent || politica': await w.isValidSignature(h, ethers.concat([sA, sP])), + 'agentul singur (65 de octeti)': await w.isValidSignature(h, sA), 'politica singura (65 de octeti)': await w.isValidSignature(h, sP), + 'agentul de doua ori': await w.isValidSignature(h, ethers.concat([sA, sA])), 'politica de doua ori': await w.isValidSignature(h, ethers.concat([sP, sP])), + 'ordinea inversa': await w.isValidSignature(h, ethers.concat([sP, sA])), 'agent || cheie straina': await w.isValidSignature(h, ethers.concat([sA, sS])), + 'cheie straina || politica': await w.isValidSignature(h, ethers.concat([sS, sP])), + }; + const [primul, ...restul] = Object.entries(raspunsuri); + cer(primul[1] === MAGIC && restul.every(([, r]) => r === INVALID), + `3. pe lant, isValidSignature: DA numai pentru agent || politica; NU pentru ${restul.length} alte forme (${restul.filter(([, r]) => r !== INVALID).map(([k]) => k).join(', ') || 'toate refuzate'})`); + // 4. cumparaturile, prin facilitatorul testnetului + const agent = newAgentIdentity(); + const { policy, policyHash } = definePolicy({ agentId: agent.agentId, spend: { amount: '30000', windowSeconds: 3600, asset: assetId(NET, TOKEN.address) }, + recipients: [payee], wallet: portofel }); + const serviciu = createWallet({ policy, policyHash, evmPrivateKey: politica.privateKey, contractWallet: portofel, network: NET, token: TOKEN }); + const L = openLedger({ identity: agent, policy, policyHash }); + const cerinta = { scheme: 'exact', network: NET, amount: '10000', asset: TOKEN.address, payTo: payee, maxTimeoutSeconds: 120, extra: { name: TOKEN.name, version: TOKEN.version } }; + const rs = createResourceServer({ requirement: cerinta, facilitator: FAC, content: 'the paid content' }); + const url = await rs.listen(); deschise.push(rs.server); + const plati = []; + for (let i = 0; i < 3; i++) plati.push(await payWithAgent({ url, ledger: L, wallet: serviciu, agentEvmKey: agentEvm.privateKey })); + cer(plati.every((p) => p.paid && p.status === 200 && p.body === 'the paid content' && /^0x[0-9a-f]{64}$/.test(p.settlement.transaction || '')), + `4. trei cumparaturi de 0,01 tUSD platite din portofelul 2-din-2 prin facilitatorul testnetului si servite (${plati.map((p) => p.status + (p.reason ? ' ' + taie(p.reason) : '')).join('; ')})`); + const s0 = rs.lastPayloads[0] && rs.lastPayloads[0].payload.signature; + cer(!!s0 && ethers.dataLength(s0) === 130, '4. fiecare plata poarta 130 de octeti de semnatura: jumatatea agentului, apoi a serviciului de politica'); + const p4 = await payWithAgent({ url, ledger: L, wallet: serviciu, agentEvmKey: agentEvm.privateKey }); + cer(!p4.paid && /policy refused.*over the limit/.test(p4.reason || '') && serviciu.status().signed === 3, `4. CONTROL: a patra depaseste 0,03 pe ora: refuzata de politica, serviciul a semnat tot 3 (${taie(p4.reason)})`); + // 5. atacurile: o autorizare noua catre vanzator, semnata de o singura parte + const acum = Math.floor(Date.now() / 1000); + const au = { from: portofel, to: payee, value: '10000', validAfter: String(acum - 5), validBefore: String(acum + 100), nonce: ethers.hexlify(ethers.randomBytes(32)) }; + const d5 = digestul(au); + const a5 = agentEvm.signingKey.sign(d5).serialized, p5 = politica.signingKey.sign(d5).serialized; + const trimite = async (sig) => { const pl = { x402Version: 2, accepted: cerinta, payload: { signature: sig, authorization: au } }; + const r = await fetch(url, { headers: { 'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(pl)).toString('base64') } }); return [r.status, await r.text()]; }; + const [cA, tA] = await trimite(ethers.concat([a5, a5])); + cer(cA === 402 && /invalid_signature/.test(tA), `5. ATAC: agentul singur (jumatatea lui de doua ori) prin facilitatorul testnetului -> ${cA} (${taie(tA.match(/payment not valid: [a-z_]+/) || tA)})`); + const [cP, tP] = await trimite(ethers.concat([p5, p5])); + cer(cP === 402 && /invalid_signature/.test(tP), `5. ATAC: serviciul de politica singur (proprietarul) prin facilitatorul testnetului -> ${cP} (${taie(tP.match(/payment not valid: [a-z_]+/) || tP)})`); + const apelTok = (sig) => tUSD.transferWithAuthorization.staticCall(au.from, au.to, BigInt(au.value), BigInt(au.validAfter), BigInt(au.validBefore), au.nonce, sig, { from: dev.address }); + let refuzAgent = false; try { await apelTok(ethers.concat([a5, a5])); } catch { refuzAgent = true; } + let refuzPol = false; try { await apelTok(ethers.concat([p5, p5])); } catch { refuzPol = true; } + let primestePerechea = false; try { await apelTok(ethers.concat([a5, p5])); primestePerechea = true; } catch { primestePerechea = false; } + cer(refuzAgent && refuzPol && primestePerechea, `5. pe lant, fara facilitator (eth_call transferWithAuthorization): tokenul refuza agentul singur (${refuzAgent}) si politica singura (${refuzPol}), primeste perechea (${primestePerechea})`); + rs.server.close(); + // 6. pe lant + await new Promise((r) => setTimeout(r, 2000)); + const soldPayee = await tUSD.balanceOf(payee), soldPortofel = await tUSD.balanceOf(portofel); + cer(soldPayee === 30000n && soldPortofel === 20000n, `6. pe lant: vanzatorul are 30000, portofelul-contract 20000 (${soldPayee}, ${soldPortofel})`); + const folosite = await Promise.all(plati.map((p) => tUSD.authorizationState(portofel, nonceForEntry(p.entry.hash)))); + const atacFolosit = await tUSD.authorizationState(portofel, au.nonce); + cer(folosite.every(Boolean) && !atacFolosit, '6. pe lant: nonce-ul sha256(hash-ul intrarii) e folosit pentru fiecare plata; nonce-ul atacurilor nu'); + // 7. dosarul-dovada, verificat din afara + const dovada = { v: 1, kind: 'aere-agent-x402-payments', network: NET, token: TOKEN.address, wallet: portofel, fromBlock: rcD.blockNumber, + walletContract: { contract: 'AereAgentWallet2of2', sourceSha256: amprentaSursei(art), agentSigner: agentEvm.address, policySigner: politica.address, deployTransaction: rcD.hash }, + facilitator: FAC, policy, policyHash, ledger: L.export(), + payments: plati.map((p) => ({ entrySeq: p.entry.seq, entryHash: p.entry.hash, transaction: p.settlement.transaction })), createdAt: new Date().toISOString() }; + const dir = path.join(AICI, 'dovezi-28001'); fs.mkdirSync(dir, { recursive: true }); + const f = path.join(dir, `plati-agent-2of2-${dovada.createdAt.slice(0, 19).replace(/[:T]/g, '-')}.json`); + fs.writeFileSync(f, JSON.stringify(dovada, null, 1) + '\n'); + const v = await verificaPlati(dovada, { rpc: RPC, allTransfers: true }); + cer(v.verdict === 'VALID' && v.checks.some((x) => x.pass === true && /is the 2-of-2 contract/.test(x.name)), + `7. verifica-plati pe dosarul-dovada: ${v.verdict} (${v.checks.length} verificari, printre ele: codul portofelului e contractul 2-din-2)`); + const alt = JSON.parse(JSON.stringify(dovada)); alt.walletContract.policySigner = strain.address; + const va = await verificaPlati(alt, { rpc: RPC }); + cer(va.verdict === 'INVALID', `7. CONTROL: un dosar care numeste alt semnatar de politica -> ${va.verdict} (codul de pe lant nu e cel cu acel semnatar)`); + console.log(` dosarul-dovada: ${path.relative(process.cwd(), f)}`); +} catch (e) { cer(false, `proba s-a oprit: ${taie(e.shortMessage || e.message)}`); } +finally { for (const s of deschise) { try { s.closeAllConnections && s.closeAllConnections(); s.close(); } catch { /* inchis */ } } } +console.log(`\nagent-cosign-testnet: ${ok}/${ok + rau} ${rau ? 'CU ESECURI' : 'cum trebuia'}`); +process.exitCode = rau ? 1 : 0; diff --git a/agents/x402/proba-cosign.mjs b/agents/x402/proba-cosign.mjs new file mode 100644 index 0000000..c453770 --- /dev/null +++ b/agents/x402/proba-cosign.mjs @@ -0,0 +1,98 @@ +// Proba modului cosign (portofel-contract 2-din-2, AereAgentWallet2of2.sol), fara retea: facilitatorul local emuleaza ERC-1271-ul +// contractului (logica lui adevarata o proba hardhat si testnetul). Adversarii: agentul singur, serviciul de politica singur, si un +// serviciu de politica COMPROMIS care incearca sa-l faca pe agent sa semneze alta plata decat cea din intrarea lui. +// node proba-cosign.mjs iesire 0 = toate cum trebuia +import { definePolicy } from '../agent-policy.mjs'; +import { newAgentIdentity, openLedger } from '../agent-ledger.mjs'; +import { createWallet, serve, assetId, x402Action, incarcaEthers, EIP3009_TYPES } from './wallet.mjs'; +import { payWithAgent, completeazaCosemnarea, walletOverHttp } from './client.mjs'; +import { createResourceServer } from './resource-server.mjs'; +import { createLocalFacilitator } from './facilitator-local.mjs'; + +const ethers = incarcaEthers(); +let ok = 0, rau = 0; +const cer = (c, ce) => { console.log((c ? ' OK ' : ' RAU ') + ce); c ? ok++ : rau++; }; +const NET = 'eip155:28001'; +const TOKEN = { address: '0x8215bA247a3574af8EBC36606eB437811E318FBd', name: 'AereTestUSD', version: '2' }; +const cerinta = (payTo, amount = '10000') => ({ scheme: 'exact', network: NET, amount, asset: TOKEN.address, payTo, maxTimeoutSeconds: 60, extra: { name: TOKEN.name, version: TOKEN.version } }); +const deschise = []; const inchide = (s) => { try { s.closeAllConnections && s.closeAllConnections(); s.close(); } catch { /* inchis */ } }; + +try { + const contract = ethers.Wallet.createRandom().address; // adresa portofelului-contract (emulat) + const agentEvm = ethers.Wallet.createRandom(), politica = ethers.Wallet.createRandom(); + const payee = ethers.Wallet.createRandom().address.toLowerCase(); + const agent = newAgentIdentity(); + const { policy, policyHash } = definePolicy({ agentId: agent.agentId, spend: { amount: '30000', windowSeconds: 3600, asset: assetId(NET, TOKEN.address) }, + recipients: [payee], wallet: contract }); + const wallet = createWallet({ policy, policyHash, evmPrivateKey: politica.privateKey, contractWallet: contract, network: NET, token: TOKEN }); + const fac = createLocalFacilitator({ network: NET, token: TOKEN, balances: { [contract]: '100000' }, contracts: { [contract]: { agentSigner: agentEvm.address, policySigner: politica.address } } }); + const facUrl = await fac.listen(); deschise.push(fac.server); + const rs = createResourceServer({ requirement: cerinta(payee), facilitator: facUrl }); const url = await rs.listen(); deschise.push(rs.server); + const L = openLedger({ identity: agent, policy, policyHash }); + const st = wallet.status(); + cer(st.mode === 'cosign-2of2' && st.address.toLowerCase() === contract.toLowerCase() && st.policySigner === politica.address, '1. status: modul cosign, portofelul e contractul, semnatarul de politica numit'); + + // 2. trei plati, cu jumatatea agentului adaugata de client + const plati = []; + for (let i = 0; i < 3; i++) plati.push(await payWithAgent({ url, ledger: L, wallet, agentEvmKey: agentEvm.privateKey })); + cer(plati.every((p) => p.paid) && fac.balanceOf(payee) === 30000n && fac.balanceOf(contract) === 70000n, `2. trei plati din portofelul 2-din-2, fiecare cu ambele semnaturi (${plati.map((p) => p.status).join(',')})`); + const s0 = rs.lastPayloads[0].payload.signature; + const dig0 = ethers.TypedDataEncoder.hash({ name: TOKEN.name, version: TOKEN.version, chainId: 28001, verifyingContract: TOKEN.address }, EIP3009_TYPES, + { ...rs.lastPayloads[0].payload.authorization, value: BigInt(rs.lastPayloads[0].payload.authorization.value), validAfter: BigInt(rs.lastPayloads[0].payload.authorization.validAfter), validBefore: BigInt(rs.lastPayloads[0].payload.authorization.validBefore) }); + cer(ethers.dataLength(s0) === 130 && ethers.recoverAddress(dig0, ethers.dataSlice(s0, 0, 65)) === agentEvm.address && ethers.recoverAddress(dig0, ethers.dataSlice(s0, 65, 130)) === politica.address, + '2. semnatura platii are 130 de octeti: intai agentul, apoi semnatarul de politica, peste acelasi digest'); + const p4 = await payWithAgent({ url, ledger: L, wallet, agentEvmKey: agentEvm.privateKey }); + cer(!p4.paid && /over the limit/.test(p4.reason || ''), `2. CONTROL: a patra depaseste limita -> refuzata (${(p4.reason || '').slice(0, 60)})`); + + // 3. agentul SINGUR: isi semneaza singur o autorizare catre vanzator, fara serviciu + const acum = Math.floor(Date.now() / 1000); + const a3 = { from: contract, to: ethers.getAddress(payee), value: '10000', validAfter: String(acum - 5), validBefore: String(acum + 50), nonce: ethers.hexlify(ethers.randomBytes(32)) }; + const d3 = ethers.TypedDataEncoder.hash({ name: TOKEN.name, version: TOKEN.version, chainId: 28001, verifyingContract: TOKEN.address }, EIP3009_TYPES, { ...a3, value: 10000n, validAfter: BigInt(a3.validAfter), validBefore: BigInt(a3.validBefore) }); + const trimite = async (sig) => { const pl = { x402Version: 2, accepted: cerinta(payee), payload: { signature: sig, authorization: a3 } }; + const r = await fetch(url, { headers: { 'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(pl)).toString('base64') } }); return [r.status, await r.text()]; }; + const [c3, t3] = await trimite(agentEvm.signingKey.sign(d3).serialized); + const [c3b, t3b] = await trimite(ethers.concat([agentEvm.signingKey.sign(d3).serialized, agentEvm.signingKey.sign(d3).serialized])); + cer(c3 === 402 && /invalid_signature/.test(t3) && c3b === 402 && /invalid_signature/.test(t3b), '3. ATAC: agentul singur (o jumatate, sau jumatatea lui de doua ori) nu poate plati din portofel'); + // 4. serviciul de politica SINGUR + const [c4, t4] = await trimite(ethers.concat([politica.signingKey.sign(d3).serialized, politica.signingKey.sign(d3).serialized])); + cer(c4 === 402 && /invalid_signature/.test(t4) && fac.balanceOf(payee) === 30000n, '4. ATAC: serviciul de politica singur nu poate plati din portofel'); + + // 5. serviciul de politica COMPROMIS: raspunsuri falsificate, fiecare semnat consecvent cu cheia lui; agentul trebuie sa refuze + const A = newAgentIdentity(); const pay2 = ethers.Wallet.createRandom().address.toLowerCase(); const atacator = ethers.Wallet.createRandom().address; + const p5 = definePolicy({ agentId: A.agentId, spend: { amount: '1000000', windowSeconds: 3600, asset: assetId(NET, TOKEN.address) }, wallet: contract }); + const W5 = createWallet({ policy: p5.policy, policyHash: p5.policyHash, evmPrivateKey: politica.privateKey, contractWallet: contract, network: NET, token: TOKEN }); + const L5 = openLedger({ identity: A, policy: p5.policy, policyHash: p5.policyHash }); + const req5 = cerinta(pay2); const e5 = L5.record(x402Action(contract, { url: 'http://x/5' }, req5)).entry; + const bun = await W5.authorize({ requirements: req5, resource: { url: 'http://x/5' }, ledger: L5.export() }); + const dom = { name: TOKEN.name, version: TOKEN.version, chainId: 28001, verifyingContract: TOKEN.address }; + const refa = (schimba, cheie = politica) => { const pl = JSON.parse(JSON.stringify(bun.paymentPayload)); schimba(pl.payload.authorization); + const au = pl.payload.authorization; const dg = ethers.TypedDataEncoder.hash(dom, EIP3009_TYPES, { ...au, value: BigInt(au.value), validAfter: BigInt(au.validAfter), validBefore: BigInt(au.validBefore) }); + return { payload: pl, cosign: { digest: dg, policySignature: cheie.signingKey.sign(dg).serialized } }; }; + const judeca = (x) => completeazaCosemnarea({ payload: x.payload, req: req5, status: W5.status(), entryHash: e5.hash, agentEvmKey: agentEvm.privateKey, cosign: x.cosign }); + cer(bun.ok && judeca({ payload: bun.paymentPayload, cosign: bun.cosign }).ok, '5. CONTROL: raspunsul cinstit al serviciului e completat de agent'); + cer(/another recipient or amount/.test(judeca(refa((a) => { a.to = atacator; })).error || ''), '5. ATAC: serviciul schimba destinatarul -> agentul refuza sa semneze'); + cer(/not from the 2-of-2 wallet/.test(judeca(refa((a) => { a.from = atacator; })).error || ''), '5. ATAC: serviciul schimba platitorul (alt portofel) -> agentul refuza'); + cer(/another recipient or amount/.test(judeca(refa((a) => { a.value = '999999'; })).error || ''), '5. ATAC: serviciul schimba suma -> agentul refuza'); + cer(/nonce is not/.test(judeca(refa((a) => { a.nonce = ethers.hexlify(ethers.randomBytes(32)); })).error || ''), "5. ATAC: serviciul pune alt nonce decat intrarea agentului -> agentul refuza"); + cer(/validity/.test(judeca(refa((a) => { a.validBefore = String(Number(a.validBefore) + 86400); })).error || ''), '5. ATAC: serviciul lungeste termenul autorizarii cu o zi -> agentul refuza'); + cer(/digest the wallet signed/.test(judeca({ payload: bun.paymentPayload, cosign: { ...bun.cosign, digest: '0x' + 'ab'.repeat(32) } }).error || ''), '5. ATAC: serviciul declara alt digest decat cel pe care il calculeaza agentul -> agentul refuza'); + cer(/not signed by the declared policy signer/.test(judeca(refa(() => {}, ethers.Wallet.createRandom())).error || ''), '5. ATAC: jumatatea serviciului semnata de alta cheie decat semnatarul declarat -> agentul refuza'); + + // 6. fara cheia agentului, clientul se opreste cu motivul + const B = newAgentIdentity(); const p6 = definePolicy({ agentId: B.agentId, spend: { amount: '100000', windowSeconds: 3600, asset: assetId(NET, TOKEN.address) }, recipients: [payee], wallet: contract }); + const W6 = createWallet({ policy: p6.policy, policyHash: p6.policyHash, evmPrivateKey: politica.privateKey, contractWallet: contract, network: NET, token: TOKEN }); + const r6 = await payWithAgent({ url, ledger: openLedger({ identity: B, policy: p6.policy, policyHash: p6.policyHash }), wallet: W6 }); + cer(!r6.paid && /agentEvmKey is missing/.test(r6.reason || ''), `6. CONTROL: fara cheia agentului, un portofel 2-din-2 nu poate plati, si clientul spune de ce (${(r6.reason || '').slice(0, 60)})`); + + // 7. serviciul cosign prin HTTP (wallet.mjs serve + walletOverHttp), cap la cap + const C7 = newAgentIdentity(); const p7 = definePolicy({ agentId: C7.agentId, spend: { amount: '10000', windowSeconds: 3600, asset: assetId(NET, TOKEN.address) }, recipients: [payee], wallet: contract }); + const W7 = createWallet({ policy: p7.policy, policyHash: p7.policyHash, evmPrivateKey: politica.privateKey, contractWallet: contract, network: NET, token: TOKEN }); + const srv7 = await serve(W7, { port: 0 }); deschise.push(srv7); + const H7 = walletOverHttp(`http://127.0.0.1:${srv7.address().port}`); + const st7 = await H7.status(); + const r7 = await payWithAgent({ url, ledger: openLedger({ identity: C7, policy: p7.policy, policyHash: p7.policyHash }), wallet: H7, agentEvmKey: agentEvm.privateKey }); + cer(st7.mode === 'cosign-2of2' && st7.policySigner === politica.address && r7.paid && fac.balanceOf(payee) === 40000n, + `7. prin HTTP: /status spune modul si semnatarul de politica, iar plata cu jumatatea agentului trece (${r7.status}${r7.reason ? ' ' + r7.reason.slice(0, 60) : ''})`); +} catch (e) { cer(false, `proba s-a oprit: ${String(e.message || e).slice(0, 160)}`); } finally { for (const s of deschise) inchide(s); } +console.log(`\nagent-cosign: ${ok}/${ok + rau} ${rau ? 'CU ESECURI' : 'cum trebuia'}`); +process.exitCode = rau ? 1 : 0; diff --git a/agents/x402/proba-verifica-plati.mjs b/agents/x402/proba-verifica-plati.mjs index 7efb312..f61a748 100644 --- a/agents/x402/proba-verifica-plati.mjs +++ b/agents/x402/proba-verifica-plati.mjs @@ -1,5 +1,6 @@ // Proba verificatorului de plati (verifica-plati.mjs), fara retea: un RPC local care serveste raspunsurile INREGISTRATE de pe testnetul -// 28001 pentru dosarul-dovada din dovezi-28001/ (inregistreaza-rpc.mjs), deci date reale de pe lant. Fiecare verificare are cazul ei +// 28001 pentru cele doua dosare-dovada din dovezi-28001/ (portofelul EOA si portofelul-contract 2-din-2, inregistreaza-rpc.mjs), deci +// date reale de pe lant. Fiecare verificare are cazul ei // care trebuie sa o inroseasca, construit din datele reale schimbate intr-un singur loc. // node proba-verifica-plati.mjs iesire 0 = toate cum trebuia import fs from 'node:fs'; @@ -10,13 +11,17 @@ import { verificaPlati } from './verifica-plati.mjs'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const D = path.join(AICI, 'dovezi-28001'); -const dovadaF = fs.readdirSync(D).filter((n) => n.startsWith('plati-agent-') && n.endsWith('.json')).sort().pop(); -const inregF = fs.readdirSync(D).filter((n) => n.startsWith('rpc-inregistrat-') && n.endsWith('.json')).sort().pop(); +// doua rulari pe 28001: portofelul EOA (plati-agent-) si portofelul-contract 2-din-2 (plati-agent-2of2-), fiecare cu inregistrarea ei +const ultimul = (re) => fs.readdirSync(D).filter((n) => re.test(n)).sort().pop(); +const dovadaF = ultimul(/^plati-agent-\d.*\.json$/), inregF = ultimul(/^rpc-inregistrat-\d.*\.json$/); +const dovada2F = ultimul(/^plati-agent-2of2-.*\.json$/), inreg2F = ultimul(/^rpc-inregistrat-2of2-.*\.json$/); let ok = 0, rau = 0; const cer = (c, ce) => { console.log((c ? ' OK ' : ' RAU ') + ce); c ? ok++ : rau++; }; -if (!dovadaF || !inregF) { console.log(`NEMASURAT: lipseste dosarul-dovada sau inregistrarea RPC in ${D}`); process.exit(2); } +if (!dovadaF || !inregF || !dovada2F || !inreg2F) { console.log(`NEMASURAT: lipseste un dosar-dovada sau o inregistrare RPC in ${D}`); process.exit(2); } const dovada = JSON.parse(fs.readFileSync(path.join(D, dovadaF), 'utf8')); const inreg = JSON.parse(fs.readFileSync(path.join(D, inregF), 'utf8')); +const dovada2 = JSON.parse(fs.readFileSync(path.join(D, dovada2F), 'utf8')); +const inreg2 = JSON.parse(fs.readFileSync(path.join(D, inreg2F), 'utf8')); const copie = (o) => JSON.parse(JSON.stringify(o)); // un RPC local peste inregistrare (sau peste o varianta schimbata a ei) @@ -26,6 +31,7 @@ async function rpcDin(rec) { if (q.method === 'eth_chainId') result = rec.chainId; else if (q.method === 'eth_getTransactionReceipt') result = rec.receipts[String(q.params[0]).toLowerCase()] || null; else if (q.method === 'eth_getLogs') result = rec.transfersOut; + else if (q.method === 'eth_getCode') result = rec.code || '0x'; res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ jsonrpc: '2.0', id: q.id, result })); }); }); await new Promise((r) => srv.listen(0, '127.0.0.1', r)); @@ -58,6 +64,18 @@ try { cer(v9.verdict === 'UNMEASURED', `9. CONTROL: un RPC al altui lant (2800) -> ${v9.verdict}, nu VALID si nu INVALID`); const v10 = await verificaPlati(dovada, { rpc: 'http://127.0.0.1:9', allTransfers: true }); cer(v10.verdict === 'UNMEASURED', `10. CONTROL: un RPC care nu raspunde -> ${v10.verdict}`); + + // portofelul-contract 2-din-2: codul de pe lant trebuie sa fie contractul compilat cu cei doi semnatari din dosar + const e2 = /is the 2-of-2 contract/; + const v11 = await judeca(dovada2, inreg2); + cer(v11.verdict === 'VALID' && v11.checks.every((c) => c.pass === true) && v11.checks.some((c) => e2.test(c.name)), + `11. dosarul portofelului 2-din-2, peste raspunsurile reale de pe 28001: VALID (${v11.checks.length} verificari, cu codul contractului)`); + const r12 = copie(inreg2); const pozitie = r12.code.length - 120; r12.code = r12.code.slice(0, pozitie) + (r12.code[pozitie] === '0' ? '1' : '0') + r12.code.slice(pozitie + 1); + cer(pica(await judeca(dovada2, r12), e2), '12. CONTROL: un singur octet schimbat in codul de pe lant -> INVALID'); + const d13 = copie(dovada2); d13.walletContract.agentSigner = '0x' + '22'.repeat(20); + cer(pica(await judeca(d13, inreg2), e2), '13. CONTROL: dosarul numeste alt semnatar al agentului decat cel din codul de pe lant -> INVALID'); + const r14 = copie(inreg2); delete r14.code; + cer(pica(await judeca(dovada2, r14), e2), '14. CONTROL: la adresa portofelului nu e niciun cod (un cont cu o cheie), dar dosarul spune 2-din-2 -> INVALID'); } catch (e) { cer(false, `proba s-a oprit: ${String(e.message || e).slice(0, 160)}`); } console.log(`\nverifica-plati: ${ok}/${ok + rau} ${rau ? 'CU ESECURI' : 'cum trebuia'}`); process.exitCode = rau ? 1 : 0; diff --git a/agents/x402/recompileaza-contract.mjs b/agents/x402/recompileaza-contract.mjs new file mode 100644 index 0000000..8d90fd7 --- /dev/null +++ b/agents/x402/recompileaza-contract.mjs @@ -0,0 +1,52 @@ +#!/usr/bin/env node +// recompileaza-contract.mjs: arata ca AereAgentWallet2of2.json spune adevarul despre sursa lui (2026-09-29). Da intrarea standard a +// compilatorului din artefact (sursele si setarile) unui solc 0.8.23 ales de cel care verifica, si cere ca codul de desfasurare si +// codul de rulare sa iasa OCTET CU OCTET cele din artefact; codul de pe lant il compara apoi verifica-plati.mjs. Controlul metodei e +// inauntru: aceeasi intrare cu un singur caracter schimbat intr-un comentariu al sursei TREBUIE sa dea alt cod (amprenta metadatelor), +// altfel comparatia nu poate deosebi nimic si iesirea e NEMASURAT. +// node recompileaza-contract.mjs --solc iesire 0 IDENTIC, 1 DIFERIT, 2 NEMASURAT +// (solc 0.8.23: github.com/ethereum/solidity/releases/tag/v0.8.23, sau npx solc@0.8.23 cu --solcjs) +import fs from 'node:fs'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { incarcaArtefact } from './contract-2of2.mjs'; + +const a = process.argv.slice(2); const get = (f) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : undefined; }; +const solc = get('--solc'); const solcjs = a.includes('--solcjs'); +if (!solc && !solcjs) { console.log('usage: node recompileaza-contract.mjs --solc | --solcjs (runs npx solc@0.8.23)'); process.exit(2); } +const art = incarcaArtefact(); +const cheama = (args, intrare) => solcjs + ? spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['--yes', 'solc@0.8.23', ...args], { input: intrare, encoding: 'utf8', maxBuffer: 1 << 28, shell: process.platform === 'win32', timeout: 600000 }) + : spawnSync(solc, args, { input: intrare, encoding: 'utf8', maxBuffer: 1 << 28, timeout: 600000 }); +const ver = cheama(['--version']); +const versiune = ((ver.stdout || '') + (ver.stderr || '')).match(/0\.8\.23\+commit\.[0-9a-f]+/); +if (!versiune) { console.log(`NEMASURAT: the compiler is not solc 0.8.23 (${String((ver.stdout || ver.stderr || (ver.error && ver.error.message) || '').trim()).slice(0, 100)})`); process.exit(2); } +const compileaza = (input) => { + const r = cheama(['--standard-json'], JSON.stringify(input)); + let o = null; try { o = JSON.parse(r.stdout); } catch { return { eroare: `no JSON from the compiler (exit ${r.status})` }; } + const erori = (o.errors || []).filter((e) => e.severity === 'error'); + if (erori.length) return { eroare: erori.map((e) => e.formattedMessage || e.message).join(' | ').slice(0, 200) }; + const c = o.contracts && o.contracts[art.sourcePath] && o.contracts[art.sourcePath][art.contractName]; + return c ? { bytecode: '0x' + c.evm.bytecode.object, deployedBytecode: '0x' + c.evm.deployedBytecode.object } : { eroare: 'the output has no such contract' }; +}; +const r = compileaza(art.standardJsonInput); +if (r.eroare) { console.log(`NEMASURAT: ${r.eroare}`); process.exit(2); } +// controlul metodei: un comentariu schimbat trebuie sa schimbe codul +const alt = JSON.parse(JSON.stringify(art.standardJsonInput)); +alt.sources[art.sourcePath].content = alt.sources[art.sourcePath].content.replace('HONEST SCOPE', 'HONEST SCOPF'); +const rc = compileaza(alt); +if (rc.eroare || alt.sources[art.sourcePath].content === art.standardJsonInput.sources[art.sourcePath].content || rc.deployedBytecode === r.deployedBytecode) { + console.log(`NEMASURAT: the method control failed (a changed comment did not change the code${rc.eroare ? ': ' + rc.eroare : ''})`); process.exit(2); +} +// copia de citit a sursei (contract/AereAgentWallet2of2.sol, in pachetul publicat) trebuie sa fie chiar sursa compilata +const citibil = path.join(path.dirname(fileURLToPath(import.meta.url)), 'contract', 'AereAgentWallet2of2.sol'); +const copieBuna = !fs.existsSync(citibil) || fs.readFileSync(citibil, 'utf8').replace(/\r\n/g, '\n') === art.standardJsonInput.sources[art.sourcePath].content; +const bun = r.bytecode === art.bytecode && r.deployedBytecode === art.deployedBytecode && copieBuna; +console.log(` compiler: solc ${versiune[0]}${solcjs ? ' (solcjs)' : ''}`); +console.log(fs.existsSync(citibil) ? ` ${copieBuna ? 'OK ' : 'FAIL'} contract/AereAgentWallet2of2.sol is the compiled source` : ' -- no readable copy of the source next to this script (contract/AereAgentWallet2of2.sol)'); +console.log(` ${r.bytecode === art.bytecode ? 'OK ' : 'FAIL'} creation code: ${(r.bytecode.length - 2) / 2} bytes`); +console.log(` ${r.deployedBytecode === art.deployedBytecode ? 'OK ' : 'FAIL'} runtime code: ${(r.deployedBytecode.length - 2) / 2} bytes`); +console.log(` OK method control: one changed character in a comment gives another runtime code`); +console.log(bun ? 'IDENTICAL: the artifact is the compilation of its source' : 'DIFFERENT: the artifact is not the compilation of its source, or the readable copy is not that source'); +process.exitCode = bun ? 0 : 1; diff --git a/agents/x402/verifica-plati.mjs b/agents/x402/verifica-plati.mjs index 2d554f5..d9eedc9 100644 --- a/agents/x402/verifica-plati.mjs +++ b/agents/x402/verifica-plati.mjs @@ -6,13 +6,17 @@ // 2. fiecare plata numeste o intrare a registrului: plata permisa, din portofel, catre destinatar, cu suma si activul; // 3. pe lant, tranzactia platii are status 1 si emite, din contractul activului, AuthorizationUsed(portofel, nonce) cu nonce = // sha256(hash-ul intrarii) si Transfer(portofel, destinatar, suma) - deci plata de pe lant e chiar cea din registru; -// 4. cu --all-transfers: ORICE Transfer din portofel in intervalul de blocuri e una din platile de mai sus (nicio plata fara intrare). +// 4. cu --all-transfers: ORICE Transfer din portofel in intervalul de blocuri e una din platile de mai sus (nicio plata fara intrare); +// 5. cand dosarul numeste un portofel-contract 2-din-2 (walletContract: {agentSigner, policySigner}): codul de pe lant al portofelului e +// EXACT codul compilat din AereAgentWallet2of2.sol (artefactul de langa verificator) cu cei doi semnatari in locul imutabilelor, deci +// din portofel nu a putut plati nici agentul singur, nici serviciul de politica singur. // Ce nu dovedeste: ca serviciul cumparat a fost livrat; ca portofelul nu a semnat si autorizari nedecontate (acelea nu misca bani). // node verifica-plati.mjs --rpc [--all-transfers] iesire 0 VALID, 1 INVALID, 2 NEMASURAT import fs from 'node:fs'; import { pathToFileURL } from 'node:url'; import { verifyLedger } from '../agent-ledger.mjs'; import { incarcaEthers, nonceForEntry, assetId } from './wallet.mjs'; +import { incarcaArtefact, codulAsteptat, amprentaSursei } from './contract-2of2.mjs'; export async function verificaPlati(d, { rpc, allTransfers = false, fetchImpl = fetch } = {}) { const ethers = incarcaEthers(); @@ -53,6 +57,17 @@ export async function verificaPlati(d, { rpc, allTransfers = false, fetchImpl = ok(`every Transfer out of the wallet since block ${d.fromBlock || 0} is a payment in the ledger (${logs.length} transfers)`, straine.length === 0, straine.map((l) => l.transactionHash.slice(0, 12)).join(', ')); } catch (x) { rez.push({ name: 'all transfers out of the wallet', pass: null, detail: x.message }); } } + if (d.walletContract) { + const wc = d.walletContract; let art = null, asteptat = null; + try { art = incarcaArtefact(); asteptat = codulAsteptat(art, wc); } catch (x) { rez.push({ name: 'the 2-of-2 wallet artifact', pass: null, detail: x.message }); } + if (asteptat) { + try { + const cod = String(await apel('eth_getCode', [d.wallet, 'latest'])).toLowerCase(); + ok(`the wallet is the 2-of-2 contract (AereAgentWallet2of2, source ${String(amprentaSursei(art)).slice(0, 12)}..): its code on chain is the compiled code with agent signer ${String(wc.agentSigner).slice(0, 10)}.. and policy signer ${String(wc.policySigner).slice(0, 10)}..`, + cod === asteptat, cod === asteptat ? '' : cod === '0x' ? 'no code at the wallet address' : `the code on chain differs: ${(cod.length - 2) / 2} bytes, expected ${(asteptat.length - 2) / 2}`); + } catch (x) { rez.push({ name: 'the wallet code on chain', pass: null, detail: x.message }); } + } + } const verdict = rez.some((c) => c.pass === false) ? 'INVALID' : rez.some((c) => c.pass === null) ? 'UNMEASURED' : 'VALID'; return { verdict, checks: rez }; } diff --git a/agents/x402/wallet.mjs b/agents/x402/wallet.mjs index 6422084..4ab9b1b 100644 --- a/agents/x402/wallet.mjs +++ b/agents/x402/wallet.mjs @@ -18,12 +18,17 @@ // de intrarea din registru, si aceeasi intrare nu poate plati de doua ori (nici la portofel, nici pe lant). Intoarce PaymentPayload-ul // x402 v2, de pus de agent in antetul PAYMENT-SIGNATURE, si un plic AIP-23 cu decizia. // +// DOUA MODURI. `eoa`: portofelul E adresa cheii pe care o tine; cine tine cheia (proprietarul) poate plati si fara agent. `cosign` +// (2026-09-29, `contractWallet`): banii stau intr-un portofel-contract 2-din-2 (contracts/contracts/x402/AereAgentWallet2of2.sol, +// ERC-1271) care cere semnatura agentului SI a acestui serviciu; serviciul semneaza aici numai jumatatea lui, peste acelasi digest +// EIP-712, iar agentul isi adauga jumatatea dupa ce isi recalculeaza singur digestul (client.mjs). Nici agentul, nici proprietarul +// nu pot plati singuri. +// // CE NU FACE: nu trimite tranzactii (decontarea o face facilitatorul x402 al serverului de resurse); nu tine alt activ decat unul -// EIP-3009 configurat; nu e custodie fara incredere: cine tine cheia portofelului (proprietarul) poate plati fara agent. Un portofel- -// contract 2-din-2 (agentul + politica, ERC-1271) ar scoate si increderea asta; nu e facut. +// EIP-3009 configurat. // // node wallet.mjs serve --config wallet.json [--port 8793] serviciul HTTP (POST /authorize, POST /revocations, GET /status) -// wallet.json: { policy, policyHash, network, token:{address,name,version}, evmKeyFile, stateFile, toleranceSeconds?, maxValiditySeconds? } +// wallet.json: { policy, policyHash, network, token:{address,name,version}, evmKeyFile, stateFile, contractWallet?, toleranceSeconds?, maxValiditySeconds? } import fs from 'node:fs'; import path from 'node:path'; import http from 'node:http'; @@ -76,7 +81,11 @@ export function createWallet(c) { const { policy, policyHash } = hashPolicy(c.policy); if (c.policyHash != null && String(c.policyHash).toLowerCase() !== policyHash) throw new Error('agent-wallet: the policy does not hash to the pinned policyHash'); const cont = new ethers.Wallet(String(c.evmPrivateKey || '').trim()); - const adresa = cont.address.toLowerCase(); + // in modul cosign portofelul e contractul, iar cheia tinuta aici e numai a semnatarului de politica + if (c.contractWallet != null && !ADRESA.test(String(c.contractWallet))) throw new Error('agent-wallet: contractWallet must be an address'); + const cosign = c.contractWallet != null; + const platitor = cosign ? ethers.getAddress(c.contractWallet) : cont.address; + const adresa = platitor.toLowerCase(); if (policy.wallet !== adresa) throw new Error('agent-wallet: the policy does not name this wallet (policy.wallet must be its address)'); if (!/^eip155:[0-9]+$/.test(String(c.network))) throw new Error('agent-wallet: network must be eip155:'); if (!c.token || !ADRESA.test(String(c.token.address)) || !c.token.name || !c.token.version) throw new Error('agent-wallet: token needs address, name and version (its EIP-712 domain)'); @@ -95,6 +104,7 @@ export function createWallet(c) { const sesiune = sessionId(policy.agentId, policyHash); const S = stare.sessions[sesiune] || (stare.sessions[sesiune] = { anchors: [], last: null, lastEntry: null }); const salveaza = () => { if (c.saveState) c.saveState(stare); }; + const semneazaDigest = async (d) => cont.signingKey.sign(d).serialized; const refuz = (error, extra = {}) => ({ ok: false, error, ...extra }); async function autorizeaza({ requirements: req, resource = null, ledger } = {}) { @@ -137,18 +147,22 @@ export function createWallet(c) { const d = checkAction(policy, { ...a, at: Number(b.at) }, stare.signed.map((s) => ({ amount: s.amount, at: s.at })), { approvers: aprobatori }); if (!d.allowed) return refuz(`by what this wallet has signed: ${d.reason}`); // semnatura EIP-3009 - const authorization = { from: cont.address, to: ethers.getAddress(req.payTo), value: String(req.amount), validAfter: String(t - 5), + const authorization = { from: platitor, to: ethers.getAddress(req.payTo), value: String(req.amount), validAfter: String(t - 5), validBefore: String(t + Math.min(timeout, VALID)), nonce: nonceForEntry(e.hash) }; - const signature = await cont.signTypedData(domeniu, TIPURI, { ...authorization, value: BigInt(authorization.value), + const digest = ethers.TypedDataEncoder.hash(domeniu, TIPURI, { ...authorization, value: BigInt(authorization.value), validAfter: BigInt(authorization.validAfter), validBefore: BigInt(authorization.validBefore) }); + // aceeasi semnatura ECDSA peste digestul EIP-712 in ambele moduri (in eoa e chiar semnatura EIP-712 a platitorului). Semnarea e + // asincrona dinadins (un semnatar din afara procesului, KMS sau HSM, se leaga aici): de aceea cererile trec prin coada de mai jos. + const semnatura = await semneazaDigest(digest); S.anchors.push({ seq: e.seq, hash: e.hash, at: t }); S.last = { seq: e.seq, hash: e.hash }; S.lastEntry = e; stare.signed.push({ seq: e.seq, entryHash: e.hash, amount: String(req.amount), at: Number(b.at), payTo: authorization.to, nonce: authorization.nonce }); salveaza(); - const paymentPayload = { x402Version: 2, ...(resource ? { resource } : {}), accepted: req, payload: { signature, authorization }, + const paymentPayload = { x402Version: 2, ...(resource ? { resource } : {}), accepted: req, payload: { signature: cosign ? null : semnatura, authorization }, extensions: { 'aere-agent-ledger': { agentId: ledger.agentId, policyHash, session: ledger.session, entrySeq: e.seq, entryHash: e.hash } } }; - return { ok: true, paymentPayload, header: Buffer.from(JSON.stringify(paymentPayload), 'utf8').toString('base64'), - receipt: { entrySeq: e.seq, entryHash: e.hash, nonce: authorization.nonce, from: cont.address, to: authorization.to, value: authorization.value }, + return { ok: true, paymentPayload, header: cosign ? null : Buffer.from(JSON.stringify(paymentPayload), 'utf8').toString('base64'), + ...(cosign ? { cosign: { digest, policySignature: semnatura, order: 'agent signature, then policy signature (65 + 65 bytes)' } } : {}), + receipt: { entrySeq: e.seq, entryHash: e.hash, nonce: authorization.nonce, from: platitor, to: authorization.to, value: authorization.value }, decision: decisionEnvelope({ policyHash, action: a, decision: d, createdAt: new Date(t * 1000).toISOString() }) }; } // cererile se judeca UNA CATE UNA: verificarea si semnatura au un `await` intre ele, iar doua cereri deodata ar trece amandoua de @@ -161,11 +175,12 @@ export function createWallet(c) { stare.revocations.push(rv); salveaza(); return { ok: true, revokedAt: r.revokedAt }; } - const status = () => ({ version: VERSION, address: cont.address, network, asset: token, agentId: policy.agentId, policyHash, + const status = () => ({ version: VERSION, mode: cosign ? 'cosign-2of2' : 'eoa', address: platitor, ...(cosign ? { policySigner: cont.address } : {}), + network, asset: token, tokenName: String(c.token.name), tokenVersion: String(c.token.version), agentId: policy.agentId, policyHash, lastSeq: S.last ? S.last.seq : null, signed: stare.signed.length, revocations: stare.revocations.length }); // starea, ca sa fie pastrata peste o schimbare de politica (acelasi agent, acelasi portofel): o copie, nu referinta const exportState = () => JSON.parse(JSON.stringify(stare)); - return { address: cont.address, network, token, policyHash, authorize, addRevocation, status, exportState }; + return { address: platitor, mode: cosign ? 'cosign-2of2' : 'eoa', network, token, policyHash, authorize, addRevocation, status, exportState }; } // ---------------------------------------------------------------- serviciul HTTP (numai pe 127.0.0.1 implicit)