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": "t7gcCjMnIVJDtOCuPOXD3ciAGJN9BJkrClLFSCYdYrrmxnyYyQ4VQ0TqKUUjf3l2OKI3+/2Fb+tyWZ8W8D7LH7BQcXR6MO/XbLBIkSri9+fgXE1306omPvzhn7uyndvWb1SBQzNy34E5WWBy5uissSBSZfFVVjdmi1dPyGk+L6kgi63SiwSvbdY1RE70b2E0t/nFF7wTOrtNriiQgBtDIy2206OSIyHews00T4IRPPHHXMqCJUYCMIfNuknMFD37dGKxjY7bfPeJq/0J4aWXC+jo01PYjd1RKn0V+yk9ad5oZFrijmF4ibci7kDhXQbrQhZCwrVcXEsj/bPWuKv0HKtQzrBynjjPOjPHB5kVmrl72j0NAUE5LyTXTo+fo7qi/a9eimVGuTH9WLGXEzgST06SOQHUCqT6kM9aFlW2zC4BhG3qtkQ2XkHIcNL4OoCBr3I8kmiov1hsupgk1wA+lw5OnSPXjTgS/5rfXU0XmgQsP0Yr7q1XX0tqKcxgqsufT7ynCcORT1vncQm/p2/G1QAWTm3ehsNvvcoNwYvKCiBdsojQkgCxbMQ1RF6FH29i8JD3/Pob8M9TaZzu61oMdetYb0fEXRchgyps68Ay7JUltDSsWsV/Gy50Z08JZNCs6mMKqVIE8fBxQ0VPSU9hpeWCk9DEasiwOv5EparSLdV+2wZwY82LNeSw5RAUF5UBCmBSDTyGnvbfo79n59xXub2M6vieqIWnka3hY1wEmsKf2XJvp5LUz5y6/LcRW92ll+dcVf+O5YbFxWDr7odxw7n6NbDwKJGAxydroUBZIao73pCw69RL2gqpSduoi8is3/NrY1ozyo7mZcHpMyBMlkFpdSUn7YYoAtEWFdFWvWU3KkpEsq1tOfcu5e8lWrBZm1RkBzKCiIt+8rzcoVHgHO1ZqZCDlv0J8coTsNtGf02pHIs7qyraRDyqaanrucRaVsQwtQAkh6BHUmBj7qYxKJ3wg8J2jrDqOTXuynKt1sczWxZHpBKMaK00Gcp5wLbCESfANbJ5l9YZipEe+dTIHQe6uIkTkKyBHKp5JNDaynuQdxDtXgfYVtmB4kD6d1Wn2+v8Ahs6CGUs8xZsERRIDaaPtG6i9+l/CLIlYDaMOt+SLhW+EaHzVxsox8Ffa6whjV3jg2Hi3AaHvI+vXuTGOJlZ7JP6HOQ5w2YRJlkCn/vjZmrSzNRVyV1T54oym99annWDe85JWu7AlCikQwZl8hXOU0peEdMHlPQSkUPFalRJCtN5Nobd3f8z31EHaPa5QTC1rfvXsxgfSC2r9A+GUe0VEdgc4QRc+5RoF29pxUtjjWjYuOcq3c0UZ6YZVEagw2c+20u7ISBM6iPB6j5w5SCGm8dIeER38znxKqjoqxiu63n646+2cF+7bgu+SYTtiRsLpOQIR+roKPuTtKetS5ku0TKmq4DAcNOr6hwMZLAPeLHuHYfo38TapRQ5GbEgAoxve33uqvz7WkrvL8wulJNkFGUz3tJ0rchRpE4KM+PEy/YDepl5xdwR6hMpBqUIZ/rhIwPsKzOdNHoWLyHLokpHIOAn1KdhSIVvMSUU7nAXK45Ae9IxLuTwahFPI+ajXQTFL9pSlp/LB4bKEYPv41BrdqvsL1DHkOBeE3YPvWQOpzC95eUREydNIcHXxi0yu71Cr/5+r72AmfjSoBnQh9YrpP0m6xx1a123hij0NP0YZw+YKbli1JCjaXDgyyTEYhaA9K8Epk3l5WfftEMmVlXFrZ7F996r+wIp5MqNy4Ar8kirrGNh/4RSP+Oveer0iw5YYw8HyjqQwIHPCbXbT+v8zoIBpfVQLHEi62LRSr1WoewQBBDZF9sZIxtXAsVv9BTgLOiLUxOr0ZsCZ4GDt+aPqqEOyvgBoqT7VAIBQV68X0B4dmBurBrheuYkYgJ7C5tN5WqzIl++mcbJm+uBxaOln51VHYu/bTsXxmD9dV1S61kl8K0Hv5JXuaK/DgRWsQyk4l6LcM8H9RjHHF0GcryEjugmD/1/lNErC2HLMXQgW8GG4lMz/GJCR527ZYvH/szANho6OO4nzwmyN7nf3Y+NKuZ8KRgwQScLPqR2uEymyvblI6holfmPDniBtOrT8/xb7ac27+o/y0VxnLzT2YxYPX3vUOeNsCS1Wa5NGujOn1ljgUGb8de7E46poGfiv63ZK/Yv2WstbGa1zTeexU4RbEUl0kgFkTvzmBVcWAAFztrCa6HMRHjzOtVdzf3/PdUIjxZNsEHMBSrhP6y0hXZX0jd8qzYYCJFKtn26KDOJvB3Gw2UHhHk7HQw1i/iHUcKG+MGOUgMJelNCsrlDAIAlvufYvovdzTAhU50IsB+fxqGCP7uCHM7BOnhe6AYfUNRJKtxPqlHcMmfmjApPmLR1KTp9RUMjhGYUjnk7rjBFVxTwnXwuNUWj0XaxJbM5RninaehmoAKItXR6CtJuKPkqYxvICGXhQrapo5ajcquku9Cm3ZWK04HmCtmkzb1B4UA+IJoWqw3iZ+XwsrdcC2L9nxyUDuwHDHdZ7CGgU9Dv4K5aZrFwyvc1i4H1JAag7TgK0rAUInEdC1kb/WZjclCQgZR+nvOrY9cwGCph8JrA+KtZwLrpY8JBOK9OEtoAMRjj+7UzWNmuGO+Y4eiFO0KoxBRuaiQseGHUU19T7msx8qK2RSmntlpUVb4qIYiW8lSz8My/Z5WNgg7skErcrFEHcULStWwyffZsRjKrLuGINB4chQmIR4Yiq9iFkldjNKIri57GqZAwZyBwv9OLnJJFXfg/D1VR53siwXPvMW6hRFh2E5iWLTgGMZmQzO0uIjDdYDt7aOVoqByOpMggIQtydce/69hrQRY9pLOYX9vMedMU8XfXbW5N0xfYOEGmoQDQiVcml5+PUEuSUDLOhm0KVKK1A4PTqaDIs1scCZOAE0UDyReYYZz1VJD34Il3y5BhVoR4P2mKcRrPcEtxM45onCa6zq1sfYd2ACIqQlZEf4QnvpJi5Plm2F/isLpKw9bVByEXbGrhNIm5BDk5fK087uZHA+VCyp+iSKEltPzvdxuDhYV71uSZt4KDuD+PRfm7NP/AfFbO/uMam3HVESzNaSMxBpV//mLnoyCP1i+lOMha4drUg6aPD8EQI9egJPU5OMptJaOjVZKbxxK/U9M5g6ExCpd1RTQOmA5iZYo+PCNugOxhUplBoiUQ3YLudgaypxyrEwF3++y08tDW9Kqa+OjcNpEOGGfmIVPeJJmmuDsRsHlYpz5ml7KwSN8nG4uT/ZPc46JhBqwzvJ/ZM7hnSv6BagO6MZ+zqmPDXFc7dgSmWjrk9fosKYe8l0E0cusiyIEM2JHIiDzoKDpWSitGWyOA49UboycdZfFFlRJYCV9enTbBaY+odrMyXmfn4YoHFp01Z6RpdhRj6UAN/SyTO9Qdyv7ef/yrM+eAk5tJH5yd6RKZrwbMXuOHkAxxF/GKlTkQL/3pnHgjq8SKjgM1Qv5Z2GYDZphmctE82dEF7URNPrKloBfmzjSThzJkauuEcCeOrUYjVgNVWTKL49wGZe0uFETix+VGSYJyY8N7Ruva+ZDoFs9tH9QjuGCP5F1FEpXMN8yRyi7kxXBcBLrkSiVEn5V4KS/Dl2xu/MpKbhKYDTa69y4OL7DS2Gl0wAqkaFu08ILg1EHRk4r0sxQDvxj4vaMMnWG90qLzPsolCO0h99VQSNciNZmMdcxTCE/4h1Z+tzEQUq9EnCxQ/L4r+TKokHAZWCaGIsHGccW+45ghJq6ox3ZjkCtBZcr9Lo6wzG2w9Rfyut0lQNhBcVIbfyfklWDiFiGhEYRaGEHimRz5FmYcJnaZK1huRaS1M3FThLTqd3XS+MzktNI6QTjutOAM753/Q7A1IQpXWSQofTjLxkFd4GKPPMb14+GLdPPcySDPO7bgIL45IASOt9TB9mHyEh2GRSKXl6jOtXjfCYlw8y9EAktmsPbWNAZToceIK7aMBvHZOIQcQK5uInfH8k4LE2v6Dakh3Q59MlIHfCQ6DdQN+N5KxuML+CSsai7yHVTinwr4mUp4DiKiB5JbJEdQt9CefoLa2c1moyLp/gcy3TCS/C8/mcWVbXi3wJfGVJm1eDqjUV4yhDUsyx3DvhzhqmSnZcGLqUh8VrP+OlW9J/F2eIifKBTInsp+eDKjAaktl146TXcqcmGUZEL6aj4naLULc+Z1scfLwIK0OPV1p8GS6fRR9BMuL4erxyVfkkK74i1WUHzE9Z4s5gypkf3ebnjOAvdF4RlwHOX1cLLfqK5LLa+yTlAFAIcoh+YDRfTgjTZREQK3wC9G1d1TT84z6+MNEfms4W7E/ZgTLZbb5gAXHcf8gcQWmzM7bN7sT5G9zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQoMDhMX", + "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": "tm2AGn5EDZlDseaAL2tSNk6d4BHQOiPEiTEFRM3U4xYys0nOuRGxn7GmJefC/f163LCmJsEUV+cnyf7COzZwEVdkavP0UWVSHl59S2NaemRnAIr62xMLWmkYIlZHfY4dPGdMdzA8Dqg+Dl5fyQlQAr5/wwVOX7nXL2NzVw4Q29aLw53E2QvYcSKqP8zjUxOI8RTl1/diJ0Ldx84Sw6tJhmq32REO81RhOqeg3zU5dIKkskWrcbV3tYKLDcYj4wOeNQSkxlHQ0OhC+B0Rsq5cTEQXUtYbZI0ldXHpUimEVAihyL60L+BS1PJ9PE5na5z19bCpAsN5A4rzQXDZtq88Tf9DOhZlQ2Sf/9hmrCUHGBjKGlpY6XsocI2gYoou0ZiQ+deaXODroavOdsxJjuhjOCaIApQx0euVEDw4Xu3O2jPED59PV25cRyzTnJFH3PKgyZGx5clswkM4rUKtzit9ZSXsqTQ9lIuvWwTQFLhhnH5xFp8dVbwzxmSceY7tZvDYWQiU3FnD9iVo280Acurn4Gp6OE1CyNuEDvUDZ4lzFVzOSBj2i9n14KHaIskki9xucr1dlb07bKsydjNLyfU1ScUb2//REzYW8GjvOXV9qymWfrfcud3OuiQ97JNQ3Dy1zy9M1X/6pEsTuF5u0HHBtwwPX9pbnx6ajP4A9CXtI9gFymIvzZyMzOPb4OtXToN9xepye3pkr9aJtEtOQGfUzdMm75xgyRFwn2rJ5zTo1Y6xhdn1IChUDfrUTU2jzkJ2NTpLAFSM1GNm6Y9/35EKW0kQK8FDlM2wLeWS4/nHwwEqs5nXOOaLQmBUdiZGB30xgyQ7sOq+//vXKTidju34T6gyudBUgM90HUEpKhuYytKd2+YOuXV0dFadz1iu/NLnym6Gr4TM8bO25pY1710L9VXJASJoojkzTD9aQD2ZAywh2/sl2Z1ltUxdJJeQTyqaZzfH2QbIyx7JCDHOUE6THPm54lQxtScHfXEAVeGJd9McAmGfiOPLMAPRF+8CXAZxgcpxgOJrqlQPVkywTStDAh0zbYmBWhXU/WpzO6AEZl4RXvw5Ex73qS1Ha//lm6Ie3/Tx48AXDvdkNPVMyjiXt66BlC+YGoo4JITws1xpzYwTFvntAmb9UQ3SKvzrtg6/kfN3LZnlsE2teKYzuvxu9Z/j0ULmkT0IbParnQ7l7KU3VQEs4j5J6jz7lEn7QfjFNRXWjDGX/NMA1VptKfVzsxj3dsYFiSDSG4r3Amrr7cLmAjxjsDQFdHuFM5+Qh4aa8p1QZsVv0grhywq7SidDTLc7EqFq9iVfZKRs2RQE1PjXP4KyDFctTH7ASSY6+mnQkdCKY+BOhLpKhcBuniwAlLCbW3OiPkgeLv5a9RLUizYLe3m0GP+Azrs9aEMmEuY9V3O6umAJDlpC/Ibqs/vd052rAlp4wc+wooMTcRNHMuHB3Wp84BuWPQDwL0W5YZ2QoEQ3YEA5fNYQhb5EjRo8TDgEvVlVqTJWzVjkisoTrCyNWizkRE/85GRu7ePOM0R6pMYMenEKJH8zBRXm1QYod6A6Tpppi9KdLTL1aIeSqr3xHFvlIAxGFQtVm/Vu5Msexa6BPyAwHumsQ837AweBIjQYtO7TUDj8PUeNtC1Y7JqXaXrIwPLFReO8p++BfFHirlSqTkyMMohp+C0xbFpXvWVjRtJhXo2lv6tuJU3RH328BHtFyd5BO493tzG2vnGgJfOBvPbAAhOrHW08mmt3O5aHpv2/RrJhnC93aHOyFwvsgVR3tDuCRqB7M/tp7OjTjzOBcaxExBu31ahCf4isBY1vwE1ycU3PKoslbv3EFGdqD6oIno1lVyUvDdgorgwEyxUrhfy5fDyyVjI14/Fnyu5s0lzotnQ1StXGSewZGfOAdTrRuq5CYWfahv+CcUJEAUWC1MAz6Z9yKoyuLjr5wUBzVlMpeDh3vnX1fIfMFxd9Kb7NkPD84LOUCgaAcdtuMrDYv8tXQIAH/I0p2fpydN3EqQhwmS6HFSllUPEg+iVG6EuZTdbHG2NeB1m2vR+95lCV6CB5ImT8gKYlb/9KZ1+LWSdSnr8Z/LOaeqb43q5yjW6j7c4H6rUMuYjn7pSIKJeJsMakTUPVgUmoRgmNbHO+zvvNBUUX8rl8BVHodW4PCZ9ybun/Pa3Ynzhc5/UiEkOBiD+07OGIQt7rXKHWt0b3O8mSOIFzeqISSjku5Bsdf69eKkeubLc/hcvovHsgxRjkvDBa6s1jupMeu38bMJf0cIo+npYYoK+wZ/oQwzLGelLOjGRD/8aGlIb3NY/f9UkVxJlMURfnVFpZg9XtE/oxTRxrqPBJcnzJ40gRzevmK4bcICBOM7dcb5rvMt2/4IM2dnijA1GeHacH7cMs4uwnhkUxnlAMB3WxH07l6R9zC5CNL/i8SR+MXJDLpmTTb6wSTJYtHrAIRVotOc9LL4Ua4+tA0OY3CE37yjUPAjqQco1npKCFvPHE1lnqVOh3o8KpQxLQ09LTHcWyPBaCfNTycBpXNaQQ1YJKFHnc/mOVcVmrzTjNaxsaR6OAYozxqQNgaX0HZlb1dqp5Rt9RRdjGYqSWX5ba6luPYWUTh0bM62JNlecc7aKJqu7a4gBs3lg4CYse14qJjJR03+7OA4b2RXQ4wxg8vCohgeLKbtrknauFkwbqH09Vm82V1b5aMndEE2LuPs8ricOS5+tVw8bUj65CbOyTENRlxvL/VLC+a3lqK2wyFdh45L84h8RFPqKB80eyZrceJfRZIp1hfi7CXanBO7N59ZicsPNODSV8tKt1ZqxJIaEg2NOXTI7Qqq9MFFMDViRgYzgEc6X/SaiXRy1VuL4wUhZ69MiDqbQIcl+yxe4nQcTw01JrPWyV+bOxT7FhBypL5WFHfDVf3WmP3Xgjd10iNgYnfEslxB8YlA7Vb7+qaysTMe/Mdcnr4S9UX/+UrgLMwocHF0DTGRTbLKNEjmrkdD0Mn2oXifPKa6Jtlz2oJVGYmxi4r/Gj51VJJTDrQ3ietJQkrXI8tBFJJomFUJyhVxcmYzkhLAAbIIvGrb8pgvOov4ja04bQvyFW89lGXP+q56mqzYWyeXQcVo/EMsOVa9xsXAdAXYeJYmrFOqJcfRhk+BNNa9mcHW6GKC1AbadlInSKllODsBPlmGATd51lj26ZLdHeLgfkabbbVFwQNduPh47r1LZ3mdMshX2ogXuTPZWyMzIkPEHFbshufL54nXQhdDBMRzAA0lC9GPRA2D4UMl1cdwkumCNFALijitwLYXBEEt4tG/dVtC3uy3ctnKV8j/Y4L8aaV8Jl0W3bFriTZAhEumYDowcit6VHWazF29/tdceeCH4/lLprOivJy3fLzv3pO//scC9IWbHEyuI2ASmpkIje6viJKw8UAeGT2nu0QD9QkEyHurarbnjxso9Bb6BhwkkAvOYNe4BovMiKmBnHxwlhByfYDslata3o6W/YtS7BFV1yP3moc59UBicpZMDMusHpldaK22oYtlcGLcF72IbG0nHD6Q8lOZGkRTLgVVtdFyKZ/ryyGPznXKP9W/ZnqJUd+IbzGIkmPrYVyadcrIkrPHRr5qF5k1wUbTaZjw8TqefDsuXUxXWBJtbzDU9R9M9NQiaoNtXRYvAFRSeJ6U646URSamgVQz1sldc5rB1Hv4CwKg0/A1EE60l7faV0D321sIq4hlwwfY+taC1snAFkknkWSrhxiXMrzDFDquOeebWDFM/U/GqBWXyCEG2xlHR9fm3hTE8z359zeDj0ge2iylR5sOZ/JU6T2+wici7GPGt3ha9v2qxR/yjfEQbYtvQYVwpw953sw89tIECqpwlHbFZR1PEVm6GBl7E7dNAkOhD3ZvvmSRfxyLiSNUgRHfWDwv36sH38wQ9ri75C1ALKxcs/ns+rnZWwI76nVSbsbYbBcn5CR+Pw8HhZX0VLxi8hR+WGUlqnDnnW8/MsOEJyEaEGjFtFHD8QUJAXiqZmL9ioIwEdVS7S/zeCqjVVMxOEwOcEuzK1UkHlVwG4idJDC0owBun+GjjRpB/Dugyfw6pvURAX2KAHHdmA2TlYUFo8LieU38YnKiOgiox9iNy80NSTup84bwo7XHhQDzwRVeao/UpxQ4k/yssG5Y5R9awhreE8jS47IQ2MJuEkNED6olwpLWsRgfV8ANo1YO3i6oO9Iap73UumK+SYQRTUK8b8UgyxbtsqDNlNIm31qT7/tji/+qcGPAWClE+65V/ysbW3Sd2TrmRz9c7Dsc2bliXS8AhrgBJyHvbPWxH6qoCtD9q+qFHznGQE1xbOUMUC/snxko2kCK6SBIUKJ9zkmuAXN1BaYYmlx9jmIY6Pnc3X4xRPmekdOGV8hbS1Ne/6O0dNfLj+/wAAAAAAAAAAAAAAAAAAAAAAChEVHB8m", + "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)