From b9504164f3eeddea01d0f8dc7d62fed1f90ed611 Mon Sep 17 00:00:00 2001 From: Andrew Westberg Date: Thu, 14 May 2026 15:04:01 +0000 Subject: [PATCH] refactor(token): restore zswap public key auth for fungible balances --- .devcontainer/devcontainer.json | 2 +- .github/ISSUE_TEMPLATE/01_bug_report.yml | 2 +- .github/actions/setup/action.yml | 2 +- README.md | 6 +- contracts/package.json | 1 + contracts/src/token/FungibleToken.compact | 242 +++++++++--------- .../src/token/test/FungibleToken.test.ts | 215 +++++++++------- .../test/mocks/MockFungibleToken.compact | 89 ++++--- .../test/simulators/FungibleTokenSimulator.ts | 100 +++++--- .../token/witnesses/FungibleTokenWitnesses.ts | 60 ++--- .../test/FungibleTokenWitnesses.test.ts | 74 +----- package.json | 2 +- packages/compact/README.md | 16 +- packages/compact/src/versions.ts | 2 +- packages/simulator/package.json | 2 +- yarn.lock | 30 ++- 16 files changed, 429 insertions(+), 416 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f1bbc195..0b0a7486 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,5 +3,5 @@ "build": { "dockerfile": "Dockerfile" }, - "postCreateCommand": "code --install-extension /tmp/compact.vsix && compact update 0.29.0" + "postCreateCommand": "code --install-extension /tmp/compact.vsix && compact update 0.31.0" } diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml index bd2712c2..101ca6dd 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -93,7 +93,7 @@ body: label: Version description: What version of Compact are you running? options: - - 0.29.0 (Default) + - 0.31.0 (Default) default: 0 validations: required: true diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 705e0694..6fb3d2c9 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -43,4 +43,4 @@ runs: if: ${{ inputs.skip-compact != 'true' }} uses: midnightntwrk/setup-compact-action@836895c8fffbbea6bd986af2b17e8941ff29d1f8 # v1 with: - compact-version: "0.29.0" + compact-version: "0.31.0" diff --git a/README.md b/README.md index 24d64d78..30f4a6e0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Generic badge](https://img.shields.io/badge/Compact%20Compiler-0.29.0-1abc9c.svg)](https://docs.midnight.network/relnotes/compact/minokawa-0-18-26-0) +[![Generic badge](https://img.shields.io/badge/Compact%20Compiler-0.31.0-1abc9c.svg)](https://docs.midnight.network/relnotes/compact/) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) This project is built on the Midnight Network. @@ -29,8 +29,8 @@ Follow Midnight's [Compact Developer Tools installation guide](https://docs.midn ```bash $ compact compile --version -Compactc version: 0.29.0 -0.29.0 +Compactc version: 0.31.0 +0.31.0 ``` ### Installation diff --git a/contracts/package.json b/contracts/package.json index cdfdb742..37a60294 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -42,6 +42,7 @@ "@openzeppelin-compact/compact": "workspace:^" }, "devDependencies": { + "@midnight-ntwrk/ledger-v8": "8.0.3", "@openzeppelin-compact/contracts-simulator": "workspace:^", "@tsconfig/node24": "^24.0.4", "@types/node": "25.7.0", diff --git a/contracts/src/token/FungibleToken.compact b/contracts/src/token/FungibleToken.compact index 23208333..d79c076f 100644 --- a/contracts/src/token/FungibleToken.compact +++ b/contracts/src/token/FungibleToken.compact @@ -14,7 +14,7 @@ pragma language_version >= 0.21.0; * https://github.com/midnightntwrk/compactc/issues/929 * * @dev Canonicalization - * All `Either, ContractAddress>` values are canonicalized before use as map keys + * All `Either` values are canonicalized before use as map keys * or in ledger writes. Canonicalization zeroes out the inactive branch of the Either, * ensuring that two values with the same active branch always resolve to the same map key * regardless of what data the inactive branch carries. Write paths are canonicalized in @@ -55,51 +55,47 @@ module FungibleToken { /** * @description Mapping from account addresses to their token balances. - * @type {Either, ContractAddress>} account - The account address. + * @type {Either} account - The account address. * @type {Uint<128>} balance - The balance of the account. * @type {Map} - * @type {Map, ContractAddress>, Uint<128>>} _balances + * @type {Map, Uint<128>>} _balances */ - export ledger _balances: Map, ContractAddress>, Uint<128>>; + export ledger _balances: Map, Uint<128>>; /** * @description Mapping from owner accounts to spender accounts and their allowances. - * @type {Either, ContractAddress>} account - The owner account address. - * @type {Either, ContractAddress>} spender - The spender account address. + * @type {Either} account - The owner account address. + * @type {Either} spender - The spender account address. * @type {Uint<128>} allowance - The amount allowed to be spent by the spender. * @type {Map>} - * @type {Map, ContractAddress>, Map, ContractAddress>, Uint<128>>>} _allowances + * @type {Map, Map, Uint<128>>>} _allowances */ - export ledger _allowances: Map, ContractAddress>, - Map, ContractAddress>, Uint<128>>>; + export ledger _allowances: Map, + Map, Uint<128>>>; export ledger _totalSupply: Uint<128>; export sealed ledger _name: Opaque<"string">; export sealed ledger _symbol: Opaque<"string">; export sealed ledger _decimals: Uint<8>; + struct ZswapCoinSecretKey { + bytes: Bytes<32>; + } + struct ZswapCoinPublicKeyPreimage { + sep: Bytes<21>; + secretKey: ZswapCoinSecretKey; + } /** - * @witness wit_FungibleTokenSK - * @description Returns the caller's secret key used in deriving the account identifier. - * - * The same key produces the same account identifier across all contracts. Users who - * desire cross-contract unlinkability should use different keys per contract. - * - * @returns {Bytes<32>} secretKey - A 32-byte cryptographically secure random value. - */ - witness wit_FungibleTokenSK(): Bytes<32>; - - /** - * @description Returns a canonical zero Either value for Bytes<32> and ContractAddress. - * This circuit returns the left variant (Bytes<32>) to avoid misleading contract-to-contract + * @description Returns a canonical zero Either value for ZswapCoinPublicKey and ContractAddress. + * This circuit returns the left variant (ZswapCoinPublicKey) to avoid misleading contract-to-contract * error messages. * - * @return {Either, ContractAddress>} - The zero value. + * @return {Either} - The zero value. */ - export pure circuit ZERO(): Either, ContractAddress> { - return Either, ContractAddress> { - is_left: true, left: default>, right: default + export pure circuit ZERO(): Either { + return Either { + is_left: true, left: default, right: default }; } @@ -201,12 +197,12 @@ module FungibleToken { * * - Contract is initialized. * - * @param {Either, ContractAddress>} account - The account id or contract address to query. + * @param {Either} account - The public key or contract address to query. * @return {Uint<128>} - The account's token balance. */ - export circuit balanceOf(account: Either, ContractAddress>): Uint<128> { + export circuit balanceOf(account: Either): Uint<128> { Initializable_assertInitialized(); - const canonAcct = Utils_canonicalize, ContractAddress>(account); + const canonAcct = Utils_canonicalize(account); if (!_balances.member(disclose(canonAcct))) { return 0; @@ -231,19 +227,21 @@ module FungibleToken { * - `to` is not the zero address. * - The caller has a balance of at least `value`. * - * @param {Either, ContractAddress>} to - The recipient of the transfer, either a user or a contract. + * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. + * @param {Bytes<32>} zswapCoinSecretKey - The caller's Zswap coin secret key. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit transfer( - to: Either, ContractAddress>, - value: Uint<128> + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> ): Boolean { Initializable_assertInitialized(); const isContractAddr = !to.is_left; assert(!isContractAddr, "FungibleToken: unsafe transfer"); - return _unsafeTransfer(to, value); + return _unsafeTransfer(to, value, zswapCoinSecretKey); } /** @@ -261,16 +259,18 @@ module FungibleToken { * - `to` is not the zero address. * - The caller has a balance of at least `value`. * - * @param {Either, ContractAddress>} to - The recipient of the transfer, either a user or a contract. + * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. + * @param {Bytes<32>} zswapCoinSecretKey - The caller's Zswap coin secret key. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit _unsafeTransfer( - to: Either, ContractAddress>, - value: Uint<128> + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> ): Boolean { Initializable_assertInitialized(); - const owner = left, ContractAddress>(_computeAccountId()); + const owner = left(deriveZswapCoinPublicKey(zswapCoinSecretKey)); _unsafeUncheckedTransfer(owner, to, value); return true; } @@ -287,17 +287,17 @@ module FungibleToken { * * - Contract is initialized. * - * @param {Either, ContractAddress>} owner - The account id or contract address of approver. - * @param {Either, ContractAddress>} spender - The account id or contract address of spender. + * @param {Either} owner - The public key or contract address of approver. + * @param {Either} spender - The public key or contract address of spender. * @return {Uint<128>} - The `spender`'s allowance over `owner`'s tokens. */ export circuit allowance( - owner: Either, ContractAddress>, - spender: Either, ContractAddress> + owner: Either, + spender: Either ): Uint<128> { Initializable_assertInitialized(); - const canonOwner = Utils_canonicalize, ContractAddress>(owner); - const canonSpender = Utils_canonicalize, ContractAddress>(spender); + const canonOwner = Utils_canonicalize(owner); + const canonSpender = Utils_canonicalize(spender); if (!_allowances.member(disclose(canonOwner)) || !_allowances.lookup(canonOwner).member(disclose(canonSpender))) { return 0; @@ -316,16 +316,18 @@ module FungibleToken { * - Contract is initialized. * - `spender` is not the zero address. * - * @param {Either, ContractAddress>} spender - The account id or ContractAddress that may spend on behalf of the caller. + * @param {Either} spender - The public key or ContractAddress that may spend on behalf of the caller. * @param {Uint<128>} value - The amount of tokens the `spender` may spend. + * @param {Bytes<32>} zswapCoinSecretKey - The caller's Zswap coin secret key. * @return {Boolean} - Returns a boolean value indicating whether the operation succeeded. */ - export circuit approve(spender: Either, ContractAddress>, - value: Uint<128> + export circuit approve(spender: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> ): Boolean { Initializable_assertInitialized(); - const owner = left, ContractAddress>(_computeAccountId()); + const owner = left(deriveZswapCoinPublicKey(zswapCoinSecretKey)); _approve(owner, spender, value); return true; } @@ -349,20 +351,22 @@ module FungibleToken { * - `to` is not a ContractAddress. * - The caller has an allowance of `fromAddress`'s tokens of at least `value`. * - * @param {Either, ContractAddress>} fromAddress - The current owner of the tokens for the transfer, either a user or a contract. - * @param {Either, ContractAddress>} to - The recipient of the transfer, either a user or a contract. + * @param {Either} fromAddress - The current owner of the tokens for the transfer, either a user or a contract. + * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. + * @param {Bytes<32>} zswapCoinSecretKey - The caller's Zswap coin secret key. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit transferFrom( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, - value: Uint<128> + fromAddress: Either, + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> ): Boolean { Initializable_assertInitialized(); const isContractAddr = !to.is_left; assert(!isContractAddr, "FungibleToken: unsafe transfer"); - return _unsafeTransferFrom(fromAddress, to, value); + return _unsafeTransferFrom(fromAddress, to, value, zswapCoinSecretKey); } /** @@ -382,19 +386,21 @@ module FungibleToken { * - `to` is not the zero address. * - The caller has an allowance of `fromAddress`'s tokens of at least `value`. * - * @param {Either, ContractAddress>} fromAddress - The current owner of the tokens for the transfer, either a user or a contract. - * @param {Either, ContractAddress>} to - The recipient of the transfer, either a user or a contract. + * @param {Either} fromAddress - The current owner of the tokens for the transfer, either a user or a contract. + * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. + * @param {Bytes<32>} zswapCoinSecretKey - The caller's Zswap coin secret key. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit _unsafeTransferFrom( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, - value: Uint<128> + fromAddress: Either, + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> ): Boolean { Initializable_assertInitialized(); - const spender = left, ContractAddress>(_computeAccountId()); + const spender = left(deriveZswapCoinPublicKey(zswapCoinSecretKey)); _spendAllowance(fromAddress, spender, value); _unsafeUncheckedTransfer(fromAddress, to, value); return true; @@ -419,14 +425,14 @@ module FungibleToken { * - `to` must not be the zero address. * - `to` must not be a ContractAddress. * - * @param {Either, ContractAddress>} fromAddress - The owner of the tokens to transfer. - * @param {Either, ContractAddress>} to - The receipient of the transferred tokens. + * @param {Either} fromAddress - The owner of the tokens to transfer. + * @param {Either} to - The recipient of the transferred tokens. * @param {Uint<128>} value - The amount of tokens to transfer. * @return {[]} - Empty tuple. */ export circuit _transfer( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, + fromAddress: Either, + to: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); @@ -450,14 +456,14 @@ module FungibleToken { * - `fromAddress` is not the zero address. * - `to` is not the zero address. * - * @param {Either, ContractAddress>} fromAddress - The owner of the tokens to transfer. - * @param {Either, ContractAddress>} to - The receipient of the transferred tokens. + * @param {Either} fromAddress - The owner of the tokens to transfer. + * @param {Either} to - The recipient of the transferred tokens. * @param {Uint<128>} value - The amount of tokens to transfer. * @return {[]} - Empty tuple. */ export circuit _unsafeUncheckedTransfer( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, + fromAddress: Either, + to: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); @@ -478,18 +484,18 @@ module FungibleToken { * * - Contract is initialized. * - * @param {Either, ContractAddress>} fromAddress - The original owner of the tokens moved (which is 0 if tokens are minted). - * @param {Either, ContractAddress>} to - The recipient of the tokens moved (which is 0 if tokens are burned). + * @param {Either} fromAddress - The original owner of the tokens moved (which is 0 if tokens are minted). + * @param {Either} to - The recipient of the tokens moved (which is 0 if tokens are burned). * @param {Uint<128>} value - The amount of tokens moved from `fromAddress` to `to`. * @return {[]} - Empty tuple. */ - circuit _update(fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, + circuit _update(fromAddress: Either, + to: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); - const canonFrom = Utils_canonicalize, ContractAddress>(fromAddress); - const canonTo = Utils_canonicalize, ContractAddress>(to); + const canonFrom = Utils_canonicalize(fromAddress); + const canonTo = Utils_canonicalize(to); if (_isTargetZero(disclose(canonFrom))) { // Mint @@ -528,11 +534,11 @@ module FungibleToken { * - `account` is not a ContractAddress. * - `account` is not the zero address. * - * @param {Either, ContractAddress>} account - The recipient of tokens minted. + * @param {Either} account - The recipient of tokens minted. * @param {Uint<128>} value - The amount of tokens minted. * @return {[]} - Empty tuple. */ - export circuit _mint(account: Either, ContractAddress>, value: Uint<128>): [] { + export circuit _mint(account: Either, value: Uint<128>): [] { Initializable_assertInitialized(); const isContractAddr = !account.is_left; assert(!isContractAddr, "FungibleToken: unsafe transfer"); @@ -553,12 +559,12 @@ module FungibleToken { * - Contract is initialized. * - `account` is not the zero address. * - * @param {Either, ContractAddress>} account - The recipient of tokens minted. + * @param {Either} account - The recipient of tokens minted. * @param {Uint<128>} value - The amount of tokens minted. * @return {[]} - Empty tuple. */ export circuit _unsafeMint( - account: Either, ContractAddress>, + account: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); @@ -578,11 +584,11 @@ module FungibleToken { * - `account` is not the zero address. * - `account` must have at least a balance of `value`. * - * @param {Either, ContractAddress>} account - The target owner of tokens to burn. + * @param {Either} account - The target owner of tokens to burn. * @param {Uint<128>} value - The amount of tokens to burn. * @return {[]} - Empty tuple. */ - export circuit _burn(account: Either, ContractAddress>, value: Uint<128>): [] { + export circuit _burn(account: Either, value: Uint<128>): [] { Initializable_assertInitialized(); assert(!_isTargetZero(account), "FungibleToken: invalid sender"); _update(account, ZERO(), value); @@ -601,19 +607,19 @@ module FungibleToken { * - `owner` is not the zero address. * - `spender` is not the zero address. * - * @param {Either, ContractAddress>} owner - The owner of the tokens. - * @param {Either, ContractAddress>} spender - The spender of the tokens. + * @param {Either} owner - The owner of the tokens. + * @param {Either} spender - The spender of the tokens. * @param {Uint<128>} value - The amount of tokens `spender` may spend on behalf of `owner`. * @return {[]} - Empty tuple. */ export circuit _approve( - owner: Either, ContractAddress>, - spender: Either, ContractAddress>, + owner: Either, + spender: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); - const canonOwner = Utils_canonicalize, ContractAddress>(owner); - const canonSpender = Utils_canonicalize, ContractAddress>(spender); + const canonOwner = Utils_canonicalize(owner); + const canonSpender = Utils_canonicalize(spender); assert(!_isTargetZero(canonOwner), "FungibleToken: invalid owner"); assert(!_isTargetZero(canonSpender), "FungibleToken: invalid spender"); @@ -622,7 +628,7 @@ module FungibleToken { // If owner doesn't exist, create and insert a new sub-map directly _allowances.insert( disclose(canonOwner), - default, ContractAddress>, Uint<128>>> + default, Uint<128>>> ); } _allowances.lookup(canonOwner).insert(disclose(canonSpender), disclose(value)); @@ -639,19 +645,19 @@ module FungibleToken { * - Contract is initialized. * - `spender` must have at least an allowance of `value` from `owner`. * - * @param {Either, ContractAddress>} owner - The owner of the tokens. - * @param {Either, ContractAddress>} spender - The spender of the tokens. + * @param {Either} owner - The owner of the tokens. + * @param {Either} spender - The spender of the tokens. * @param {Uint<128>} value - The amount of token allowance to spend. * @return {[]} - Empty tuple. */ export circuit _spendAllowance( - owner: Either, ContractAddress>, - spender: Either, ContractAddress>, + owner: Either, + spender: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); - const canonOwner = Utils_canonicalize, ContractAddress>(owner); - const canonSpender = Utils_canonicalize, ContractAddress>(spender); + const canonOwner = Utils_canonicalize(owner); + const canonSpender = Utils_canonicalize(spender); assert((_allowances.member(disclose(canonOwner)) && _allowances.lookup(canonOwner).member(disclose(canonSpender))), @@ -667,29 +673,20 @@ module FungibleToken { } /** - * @description Computes the caller's account identifier from the `wit_FungibleTokenSK` witness. + * @description Derives a Zswap coin public key from a Zswap coin secret key. * - * ## ID Derivation - * `accountId = persistentHash(secretKey)` + * ## Key Derivation + * `zswapCoinPublicKey.bytes = persistentHash(ZswapCoinPublicKeyPreimage { + * sep: "midnight:zswap-pk[v1]", + * secretKey: ZswapCoinSecretKey { bytes: zswapCoinSecretKey } + * })` * - * The result is a 32-byte commitment that uniquely identifies the caller. + * The result is the caller's Midnight shielded coin public key. * - * @returns {Bytes<32>} accountId - The computed account identifier. - */ - circuit _computeAccountId(): Bytes<32> { - return computeAccountId(wit_FungibleTokenSK()); - } - - /** - * @description Computes an account identifier without on-chain state, allowing a user to derive - * their identity commitment before submitting a token operation. - * This is the off-chain counterpart to {_computeAccountId} and produces an identical result - * given the same inputs. - * - * @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can + * @warning OpSec: The `zswapCoinSecretKey` parameter is a sensitive secret. Mishandling it can * permanently compromise the security of this system: * - * - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools, + * - **Never log or persist** the `zswapCoinSecretKey` in plaintext — avoid browser devtools, * application logs, analytics pipelines, or any observable side-channel. * - **Store offline or in secure enclaves** — hardware security modules (HSMs), * air-gapped devices, or encrypted vaults are strongly preferred over hot storage. @@ -700,27 +697,30 @@ module FungibleToken { * unverified browser extension, compromised runtime, or shared machine may expose * the key to a malicious observer. * - * ## ID Derivation - * `accountId = persistentHash(secretKey)` - * - * @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value. - * - * @returns {Bytes<32>} accountId - The computed account identifier. + * @param {Bytes<32>} zswapCoinSecretKey - The caller's Zswap coin secret key. + * @returns {ZswapCoinPublicKey} zswapCoinPublicKey - The derived Zswap coin public key. */ - export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> { - return persistentHash>>([secretKey]); + export pure circuit deriveZswapCoinPublicKey(zswapCoinSecretKey: Bytes<32>): ZswapCoinPublicKey { + return ZswapCoinPublicKey { + bytes: persistentHash(ZswapCoinPublicKeyPreimage { + sep: "midnight:zswap-pk[v1]", + secretKey: ZswapCoinSecretKey { + bytes: zswapCoinSecretKey + } + }) + }; } /** * @description Returns `true` if `target`'s active branch (as indicated by `is_left`) * holds the zero value. * - * @param {Either, ContractAddress>} target - The value to check. + * @param {Either} target - The value to check. * @returns {Boolean} - `true` if the active branch is zero, `false` otherwise. */ - circuit _isTargetZero(target: Either, ContractAddress>): Boolean { + circuit _isTargetZero(target: Either): Boolean { if (target.is_left) { - return target.left == default>; + return target.left == default; } else { return target.right == default; } diff --git a/contracts/src/token/test/FungibleToken.test.ts b/contracts/src/token/test/FungibleToken.test.ts index 9acfc209..2afd0903 100644 --- a/contracts/src/token/test/FungibleToken.test.ts +++ b/contracts/src/token/test/FungibleToken.test.ts @@ -1,24 +1,46 @@ import { + type CompactType, CompactTypeBytes, - CompactTypeVector, persistentHash, } from '@midnight-ntwrk/compact-runtime'; +import { ZswapSecretKeys } from '@midnight-ntwrk/ledger-v8'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import * as utils from '#test-utils/address.js'; import { FungibleTokenSimulator } from './simulators/FungibleTokenSimulator.js'; +type ZswapCoinPublicKey = { bytes: Uint8Array }; + // Helpers -const buildAccountIdHash = (sk: Uint8Array): Uint8Array => { - const rt_type = new CompactTypeVector(1, new CompactTypeBytes(32)); - return persistentHash(rt_type, [sk]); +const buildZswapCoinPublicKey = (sk: Uint8Array): ZswapCoinPublicKey => { + const compactType: CompactType<{ sep: Uint8Array; secretKey: { bytes: Uint8Array } }> = { + alignment() { + return new CompactTypeBytes(21) + .alignment() + .concat(new CompactTypeBytes(32).alignment()); + }, + toValue(value) { + return new CompactTypeBytes(21) + .toValue(value.sep) + .concat(new CompactTypeBytes(32).toValue(value.secretKey.bytes)); + }, + fromValue() { + throw new Error('fromValue not implemented for test helper'); + }, + }; + return { + bytes: persistentHash(compactType, { + sep: new TextEncoder().encode('midnight:zswap-pk[v1]'), + secretKey: { bytes: sk }, + }), + }; }; const zeroBytes = utils.zeroUint8Array(); -const eitherAccountId = (accountId: Uint8Array) => { +const eitherPublicKey = (publicKey: ZswapCoinPublicKey) => { return { is_left: true, - left: accountId, + left: publicKey, right: { bytes: zeroBytes }, }; }; @@ -26,7 +48,7 @@ const eitherAccountId = (accountId: Uint8Array) => { const eitherContract = (address: string) => { return { is_left: false, - left: zeroBytes, + left: { bytes: zeroBytes }, right: utils.encodeToAddress(address), }; }; @@ -40,11 +62,17 @@ const createTestSK = (label: string): Uint8Array => { const makeUser = (label: string) => { const secretKey = createTestSK(label); - const accountId = buildAccountIdHash(secretKey); - const either = eitherAccountId(accountId); - return { secretKey, accountId, either }; + const publicKey = buildZswapCoinPublicKey(secretKey); + const either = eitherPublicKey(publicKey); + return { secretKey, publicKey, either }; }; +const canonicalSeedVectors = [ + new Uint8Array(32).fill(0), + Uint8Array.from({ length: 32 }, (_, index) => index), + new Uint8Array(32).fill(0xff), +]; + // Users const OWNER = makeUser('OWNER'); const SPENDER = makeUser('SPENDER'); @@ -57,10 +85,10 @@ const OWNER_CONTRACT = eitherContract('OWNER_CONTRACT'); const RECIPIENT_CONTRACT = eitherContract('RECIPIENT_CONTRACT'); // Zero values -const ZERO_ACCOUNT = eitherAccountId(zeroBytes); +const ZERO_ACCOUNT = eitherPublicKey({ bytes: zeroBytes }); const ZERO_CONTRACT = { is_left: false, - left: zeroBytes, + left: { bytes: zeroBytes }, right: { bytes: zeroBytes }, }; @@ -81,12 +109,12 @@ let token: FungibleTokenSimulator; const ownerTypes = [ ['contract', OWNER_CONTRACT], - ['accountId', OWNER.either], + ['publicKey', OWNER.either], ] as const; const recipientTypes = [ ['contract', RECIPIENT_CONTRACT], - ['accountId', RECIPIENT.either], + ['publicKey', RECIPIENT.either], ] as const; describe('FungibleToken', () => { @@ -165,7 +193,7 @@ describe('FungibleToken', () => { it('should have zero left branch', () => { const zero = token.ZERO(); - expect(zero.left).toEqual(zeroBytes); + expect(zero.left).toEqual({ bytes: zeroBytes }); }); it('should have zero right branch', () => { @@ -212,7 +240,7 @@ describe('FungibleToken', () => { const nonCanonical = { is_left: true, - left: OWNER.accountId, + left: OWNER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -224,7 +252,7 @@ describe('FungibleToken', () => { const nonCanonical = { is_left: false, - left: new Uint8Array(32).fill(1), + left: { bytes: new Uint8Array(32).fill(1) }, right: OWNER_CONTRACT.right, }; @@ -238,7 +266,7 @@ describe('FungibleToken', () => { const nonCanonicalOwner = { is_left: true, - left: OWNER.accountId, + left: OWNER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -252,7 +280,7 @@ describe('FungibleToken', () => { const nonCanonicalSpender = { is_left: true, - left: SPENDER.accountId, + left: SPENDER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -266,7 +294,7 @@ describe('FungibleToken', () => { const nonCanonicalOwner = { is_left: false, - left: new Uint8Array(32).fill(1), + left: { bytes: new Uint8Array(32).fill(1) }, right: OWNER_CONTRACT.right, }; @@ -280,7 +308,7 @@ describe('FungibleToken', () => { const nonCanonicalSpender = { is_left: false, - left: new Uint8Array(32).fill(1), + left: { bytes: new Uint8Array(32).fill(1) }, right: RECIPIENT_CONTRACT.right, }; @@ -302,7 +330,7 @@ describe('FungibleToken', () => { }); it('should transfer partial', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); const partialAmt = AMOUNT - 1n; const txSuccess = token.transfer(RECIPIENT.either, partialAmt); @@ -313,7 +341,7 @@ describe('FungibleToken', () => { }); it('should transfer full', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); const txSuccess = token.transfer(RECIPIENT.either, AMOUNT); @@ -323,7 +351,7 @@ describe('FungibleToken', () => { }); it('should fail with insufficient balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); expect(() => { token.transfer(RECIPIENT.either, AMOUNT + 1n); @@ -331,7 +359,7 @@ describe('FungibleToken', () => { }); it('should fail with transfer from zero identity', () => { - // Inject a key that produces zero accountId — infeasible in practice, + // Inject a key that produces zero publicKey — infeasible in practice, // but we can test the zero check by using _unsafeUncheckedTransfer directly expect(() => { token._unsafeUncheckedTransfer( @@ -343,7 +371,7 @@ describe('FungibleToken', () => { }); it('should fail with transfer to zero', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); expect(() => { token.transfer(ZERO_ACCOUNT, AMOUNT); @@ -351,7 +379,7 @@ describe('FungibleToken', () => { }); it('should allow transfer of 0 tokens', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); const txSuccess = token.transfer(RECIPIENT.either, 0n); @@ -361,7 +389,7 @@ describe('FungibleToken', () => { }); it('should handle transfer with empty _balances', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token.transfer(RECIPIENT.either, 1n); @@ -369,7 +397,7 @@ describe('FungibleToken', () => { }); it('should fail when transferring to a contract', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); expect(() => { token.transfer(OWNER_CONTRACT, AMOUNT); @@ -392,7 +420,7 @@ describe('FungibleToken', () => { }); it('should transfer partial', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); const partialAmt = AMOUNT - 1n; const txSuccess = token._unsafeTransfer(recipient, partialAmt); @@ -403,7 +431,7 @@ describe('FungibleToken', () => { }); it('should transfer full', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); const txSuccess = token._unsafeTransfer(recipient, AMOUNT); @@ -413,7 +441,7 @@ describe('FungibleToken', () => { }); it('should fail with insufficient balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); expect(() => { token._unsafeTransfer(recipient, AMOUNT + 1n); @@ -421,7 +449,7 @@ describe('FungibleToken', () => { }); it('should allow transfer of 0 tokens', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); const txSuccess = token._unsafeTransfer(recipient, 0n); @@ -431,7 +459,7 @@ describe('FungibleToken', () => { }); it('should handle transfer with empty _balances', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token._unsafeTransfer(recipient, 1n); @@ -439,9 +467,9 @@ describe('FungibleToken', () => { }); }); - it('should fail with transfer to zero (accountId)', () => { + it('should fail with transfer to zero (publicKey)', () => { token._mint(OWNER.either, AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); expect(() => { token._unsafeTransfer(ZERO_ACCOUNT, AMOUNT); @@ -450,7 +478,7 @@ describe('FungibleToken', () => { it('should fail with transfer to zero (contract)', () => { token._mint(OWNER.either, AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); expect(() => { token._unsafeTransfer(ZERO_CONTRACT, AMOUNT); @@ -464,14 +492,14 @@ describe('FungibleToken', () => { }); it('should approve and update allowance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, AMOUNT); expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); }); it('should approve and update allowance for multiple spenders', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, AMOUNT); expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); @@ -483,7 +511,7 @@ describe('FungibleToken', () => { }); it('should fail when approve to zero', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); expect(() => { token.approve(ZERO_ACCOUNT, AMOUNT); @@ -493,10 +521,10 @@ describe('FungibleToken', () => { it('should transfer exact allowance and fail subsequent transfer', () => { token._mint(OWNER.either, AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, AMOUNT); - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT); expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); @@ -506,7 +534,7 @@ describe('FungibleToken', () => { }); it('should allow approve of 0 tokens', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, 0n); expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); @@ -519,7 +547,7 @@ describe('FungibleToken', () => { describe('transferFrom', () => { beforeEach(() => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, AMOUNT); token._mint(OWNER.either, AMOUNT); }); @@ -529,7 +557,7 @@ describe('FungibleToken', () => { }); it('should transferFrom spender (partial)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); const partialAmt = AMOUNT - 1n; const txSuccess = token.transferFrom( @@ -545,7 +573,7 @@ describe('FungibleToken', () => { }); it('should transferFrom spender (full)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); const txSuccess = token.transferFrom( OWNER.either, @@ -560,10 +588,10 @@ describe('FungibleToken', () => { }); it('should transferFrom and not consume infinite allowance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, MAX_UINT128); - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); const txSuccess = token.transferFrom( OWNER.either, RECIPIENT.either, @@ -579,7 +607,7 @@ describe('FungibleToken', () => { }); it('should fail when transfer amount exceeds allowance', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT + 1n); @@ -587,17 +615,17 @@ describe('FungibleToken', () => { }); it('should fail when transfer amount exceeds balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, AMOUNT + 1n); - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT + 1n); }).toThrow('FungibleToken: insufficient balance'); }); it('should fail when spender does not have allowance', () => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + token.privateState.injectZswapCoinSecretKey(UNAUTHORIZED.secretKey); expect(() => { token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT); @@ -605,7 +633,7 @@ describe('FungibleToken', () => { }); it('should fail to transferFrom to the zero address', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token.transferFrom(OWNER.either, ZERO_ACCOUNT, AMOUNT); @@ -613,7 +641,7 @@ describe('FungibleToken', () => { }); it('should fail when transferring to a contract', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token.transferFrom(OWNER.either, OWNER_CONTRACT, AMOUNT); @@ -623,7 +651,7 @@ describe('FungibleToken', () => { describe('_unsafeTransferFrom', () => { beforeEach(() => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, AMOUNT); token._mint(OWNER.either, AMOUNT); }); @@ -636,7 +664,7 @@ describe('FungibleToken', () => { recipientTypes, )('when the recipient is a %s', (_, recipient) => { it('should transferFrom spender (partial)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); const partialAmt = AMOUNT - 1n; const txSuccess = token._unsafeTransferFrom( @@ -652,7 +680,7 @@ describe('FungibleToken', () => { }); it('should transferFrom spender (full)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); const txSuccess = token._unsafeTransferFrom( OWNER.either, @@ -667,10 +695,10 @@ describe('FungibleToken', () => { }); it('should transferFrom and not consume infinite allowance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, MAX_UINT128); - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); const txSuccess = token._unsafeTransferFrom( OWNER.either, recipient, @@ -686,7 +714,7 @@ describe('FungibleToken', () => { }); it('should fail when transfer amount exceeds allowance', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT + 1n); @@ -694,17 +722,17 @@ describe('FungibleToken', () => { }); it('should fail when transfer amount exceeds balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.approve(SPENDER.either, AMOUNT + 1n); - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT + 1n); }).toThrow('FungibleToken: insufficient balance'); }); it('should fail when spender does not have allowance', () => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + token.privateState.injectZswapCoinSecretKey(UNAUTHORIZED.secretKey); expect(() => { token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT); @@ -712,8 +740,8 @@ describe('FungibleToken', () => { }); }); - it('should fail to transfer to the zero address (accountId)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should fail to transfer to the zero address (publicKey)', () => { + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token._unsafeTransferFrom(OWNER.either, ZERO_ACCOUNT, AMOUNT); @@ -721,7 +749,7 @@ describe('FungibleToken', () => { }); it('should fail to transfer to the zero address (contract)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + token.privateState.injectZswapCoinSecretKey(SPENDER.secretKey); expect(() => { token._unsafeTransferFrom(OWNER.either, ZERO_CONTRACT, AMOUNT); @@ -797,7 +825,7 @@ describe('FungibleToken', () => { }); }); - it('should fail when transfer to zero (accountId)', () => { + it('should fail when transfer to zero (publicKey)', () => { expect(() => { token._unsafeUncheckedTransfer(OWNER.either, ZERO_ACCOUNT, AMOUNT); }).toThrow('FungibleToken: invalid receiver'); @@ -815,7 +843,7 @@ describe('FungibleToken', () => { const nonCanonical = { is_left: true, - left: RECIPIENT.accountId, + left: RECIPIENT.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -826,7 +854,7 @@ describe('FungibleToken', () => { it('should canonicalize recipient contract address (zero out inactive left side)', () => { const nonCanonical = { is_left: false, - left: new Uint8Array(32).fill(1), + left: { bytes: new Uint8Array(32).fill(1) }, right: RECIPIENT_CONTRACT.right, }; @@ -838,7 +866,7 @@ describe('FungibleToken', () => { it('should canonicalize fromAddress (zero out inactive right side)', () => { const nonCanonical = { is_left: true, - left: OWNER.accountId, + left: OWNER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -865,7 +893,7 @@ describe('FungibleToken', () => { }).toThrow('FungibleToken: arithmetic overflow'); }); - it('should not mint to zero (accountId)', () => { + it('should not mint to zero (publicKey)', () => { expect(() => { token._mint(ZERO_ACCOUNT, AMOUNT); }).toThrow('FungibleToken: invalid receiver'); @@ -918,7 +946,7 @@ describe('FungibleToken', () => { }); }); - it('should not mint to zero (accountId)', () => { + it('should not mint to zero (publicKey)', () => { expect(() => { token._unsafeMint(ZERO_ACCOUNT, AMOUNT); }).toThrow('FungibleToken: invalid receiver'); @@ -933,7 +961,7 @@ describe('FungibleToken', () => { it('should canonicalize sender (zero out inactive right side)', () => { const nonCanonical = { is_left: true, - left: OWNER.accountId, + left: OWNER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -955,7 +983,7 @@ describe('FungibleToken', () => { expect(token.totalSupply()).toEqual(afterBurn); }); - it('should throw when burning from zero (accountId)', () => { + it('should throw when burning from zero (publicKey)', () => { expect(() => { token._burn(ZERO_ACCOUNT, AMOUNT); }).toThrow('FungibleToken: invalid sender'); @@ -982,7 +1010,7 @@ describe('FungibleToken', () => { it('should burn with non-canonical account (left)', () => { const nonCanonical = { is_left: true, - left: OWNER.accountId, + left: OWNER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -1012,7 +1040,7 @@ describe('FungibleToken', () => { expect(token.allowance(OWNER.either, RECIPIENT.either)).toEqual(0n); }); - it('should fail when approve from zero (accountId)', () => { + it('should fail when approve from zero (publicKey)', () => { expect(() => { token._approve(ZERO_ACCOUNT, SPENDER.either, AMOUNT); }).toThrow('FungibleToken: invalid owner'); @@ -1024,7 +1052,7 @@ describe('FungibleToken', () => { }).toThrow('FungibleToken: invalid owner'); }); - it('should fail when approve to zero (accountId)', () => { + it('should fail when approve to zero (publicKey)', () => { expect(() => { token._approve(OWNER.either, ZERO_ACCOUNT, AMOUNT); }).toThrow('FungibleToken: invalid spender'); @@ -1044,7 +1072,7 @@ describe('FungibleToken', () => { it('should canonicalize owner in allowance (zero out inactive right side)', () => { const nonCanonicalOwner = { is_left: true, - left: OWNER.accountId, + left: OWNER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -1055,7 +1083,7 @@ describe('FungibleToken', () => { it('should canonicalize spender in allowance (zero out inactive right side)', () => { const nonCanonicalSpender = { is_left: true, - left: SPENDER.accountId, + left: SPENDER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -1066,7 +1094,7 @@ describe('FungibleToken', () => { it('should canonicalize contract address owner (zero out inactive left side)', () => { const nonCanonicalOwner = { is_left: false, - left: new Uint8Array(32).fill(1), + left: { bytes: new Uint8Array(32).fill(1) }, right: OWNER_CONTRACT.right, }; @@ -1121,12 +1149,12 @@ describe('FungibleToken', () => { const nonCanonicalOwner = { is_left: true, - left: OWNER.accountId, + left: OWNER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; const nonCanonicalSpender = { is_left: true, - left: SPENDER.accountId, + left: SPENDER.publicKey, right: utils.encodeToAddress('JUNK_DATA'), }; @@ -1141,7 +1169,7 @@ describe('FungibleToken', () => { expect(token.totalSupply()).toEqual(AMOUNT); expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); + token.privateState.injectZswapCoinSecretKey(OWNER.secretKey); token.transfer(RECIPIENT.either, AMOUNT - 1n); expect(token.balanceOf(OWNER.either)).toEqual(1n); expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT - 1n); @@ -1151,19 +1179,19 @@ describe('FungibleToken', () => { expect(token.balanceOf(OWNER.either)).toEqual(0n); }); }); - describe('computeAccountId', () => { + describe('deriveZswapCoinPublicKey', () => { const users = [OWNER, SPENDER, RECIPIENT, UNAUTHORIZED]; it('should match the test helper derivation', () => { for (let i = 0; i < users.length; i++) { - expect(token.computeAccountId(users[i].secretKey)).toEqual( - users[i].accountId, + expect(token.deriveZswapCoinPublicKey(users[i].secretKey)).toEqual( + users[i].publicKey, ); } }); it('should produce distinct identifiers for distinct keys', () => { - const ids = users.map((u) => token.computeAccountId(u.secretKey)); + const ids = users.map((u) => token.deriveZswapCoinPublicKey(u.secretKey)); for (let i = 0; i < ids.length; i++) { for (let j = i + 1; j < ids.length; j++) { @@ -1171,6 +1199,19 @@ describe('FungibleToken', () => { } } }); + + it('should match canonical Midnight ledger-v8 zswap derivation vectors', () => { + for (const seed of canonicalSeedVectors) { + const keys = ZswapSecretKeys.fromSeed(seed); + const secretKeyBytes = new Uint8Array( + keys.coinSecretKey.yesIKnowTheSecurityImplicationsOfThis_serialize().slice(-32), + ); + + expect(token.deriveZswapCoinPublicKey(secretKeyBytes)).toEqual({ + bytes: new Uint8Array(Buffer.from(String(keys.coinPublicKey), 'hex')), + }); + } + }); }); }); }); diff --git a/contracts/src/token/test/mocks/MockFungibleToken.compact b/contracts/src/token/test/mocks/MockFungibleToken.compact index ace4c12b..a212b0fe 100644 --- a/contracts/src/token/test/mocks/MockFungibleToken.compact +++ b/contracts/src/token/test/mocks/MockFungibleToken.compact @@ -11,7 +11,7 @@ import CompactStandardLibrary; import "../../FungibleToken" prefix FungibleToken_; -export { ContractAddress, Either, Maybe }; +export { ContractAddress, Either, Maybe, ZswapCoinPublicKey }; /** * @description `init` is a param for testing. @@ -31,7 +31,7 @@ constructor( } } -export pure circuit ZERO(): Either, ContractAddress> { +export pure circuit ZERO(): Either { return FungibleToken_ZERO(); } @@ -51,98 +51,119 @@ export circuit totalSupply(): Uint<128> { return FungibleToken_totalSupply(); } -export circuit balanceOf(account: Either, ContractAddress>): Uint<128> { +export circuit balanceOf(account: Either): Uint<128> { return FungibleToken_balanceOf(account); } export circuit allowance( - owner: Either, ContractAddress>, - spender: Either, ContractAddress> + owner: Either, + spender: Either ): Uint<128> { return FungibleToken_allowance(owner, spender); } -export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean { - return FungibleToken_transfer(to, value); +export circuit transfer( + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> +): Boolean { + return FungibleToken_transfer(to, value, zswapCoinSecretKey); } -export circuit _unsafeTransfer(to: Either, ContractAddress>, value: Uint<128>): Boolean { - return FungibleToken__unsafeTransfer(to, value); +export circuit _unsafeTransfer( + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> +): Boolean { + return FungibleToken__unsafeTransfer(to, value, zswapCoinSecretKey); } export circuit transferFrom( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, - value: Uint<128> + fromAddress: Either, + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> ): Boolean { - return FungibleToken_transferFrom(fromAddress, to, value); + return FungibleToken_transferFrom(fromAddress, to, value, zswapCoinSecretKey); } export circuit _unsafeTransferFrom( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, - value: Uint<128> + fromAddress: Either, + to: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> ): Boolean { - return FungibleToken__unsafeTransferFrom(fromAddress, to, value); -} - -export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean { - return FungibleToken_approve(spender, value); + return FungibleToken__unsafeTransferFrom( + fromAddress, + to, + value, + zswapCoinSecretKey, + ); +} + +export circuit approve( + spender: Either, + value: Uint<128>, + zswapCoinSecretKey: Bytes<32> +): Boolean { + return FungibleToken_approve(spender, value, zswapCoinSecretKey); } export circuit _approve( - owner: Either, ContractAddress>, - spender: Either, ContractAddress>, + owner: Either, + spender: Either, value: Uint<128> ): [] { return FungibleToken__approve(owner, spender, value); } export circuit _transfer( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, + fromAddress: Either, + to: Either, value: Uint<128> ): [] { return FungibleToken__transfer(fromAddress, to, value); } export circuit _unsafeUncheckedTransfer( - fromAddress: Either, ContractAddress>, - to: Either, ContractAddress>, + fromAddress: Either, + to: Either, value: Uint<128> ): [] { return FungibleToken__unsafeUncheckedTransfer(fromAddress, to, value); } export circuit _mint( - account: Either, ContractAddress>, + account: Either, value: Uint<128> ): [] { return FungibleToken__mint(account, value); } export circuit _unsafeMint( - account: Either, ContractAddress>, + account: Either, value: Uint<128> ): [] { return FungibleToken__unsafeMint(account, value); } export circuit _burn( - account: Either, ContractAddress>, + account: Either, value: Uint<128> ): [] { return FungibleToken__burn(account, value); } export circuit _spendAllowance( - owner: Either, ContractAddress>, - spender: Either, ContractAddress>, + owner: Either, + spender: Either, value: Uint<128> ): [] { return FungibleToken__spendAllowance(owner, spender, value); } -export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> { - return FungibleToken_computeAccountId(secretKey); +export pure circuit deriveZswapCoinPublicKey( + zswapCoinSecretKey: Bytes<32> +): ZswapCoinPublicKey { + return FungibleToken_deriveZswapCoinPublicKey(zswapCoinSecretKey); } diff --git a/contracts/src/token/test/simulators/FungibleTokenSimulator.ts b/contracts/src/token/test/simulators/FungibleTokenSimulator.ts index bb467430..18b56038 100644 --- a/contracts/src/token/test/simulators/FungibleTokenSimulator.ts +++ b/contracts/src/token/test/simulators/FungibleTokenSimulator.ts @@ -5,6 +5,7 @@ import { import { type ContractAddress, type Either, + type ZswapCoinPublicKey, ledger, Contract as MockFungibleToken, } from '../../../../artifacts/MockFungibleToken/contract/index.js'; @@ -60,12 +61,12 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { super([name, symbol, decimals, init], options); } /** - * @description Returns a canonical zero Either value for Bytes<32> and ContractAddress. - * This circuit returns the left variant (Bytes<32>) to avoid misleading contract-to-contract + * @description Returns a canonical zero Either value for ZswapCoinPublicKey and ContractAddress. + * This circuit returns the left variant (ZswapCoinPublicKey) to avoid misleading contract-to-contract * error messages. * @returns The zero value. */ - public ZERO(): Either { + public ZERO(): Either { return this.circuits.pure.ZERO(); } @@ -106,7 +107,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param account The public key or contract address to query. * @returns The account's token balance. */ - public balanceOf(account: Either): bigint { + public balanceOf(account: Either): bigint { return this.circuits.impure.balanceOf(account); } @@ -118,8 +119,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @returns The `spender`'s allowance over `owner`'s tokens. */ public allowance( - owner: Either, - spender: Either, + owner: Either, + spender: Either, ): bigint { return this.circuits.impure.allowance(owner, spender); } @@ -131,10 +132,14 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @returns As per the IERC20 spec, this MUST return true. */ public transfer( - to: Either, + to: Either, value: bigint, ): boolean { - return this.circuits.impure.transfer(to, value); + return this.circuits.impure.transfer( + to, + value, + this.privateState.getCurrentZswapCoinSecretKey(), + ); } /** @@ -144,10 +149,14 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @returns As per the IERC20 spec, this MUST return true. */ public _unsafeTransfer( - to: Either, + to: Either, value: bigint, ): boolean { - return this.circuits.impure._unsafeTransfer(to, value); + return this.circuits.impure._unsafeTransfer( + to, + value, + this.privateState.getCurrentZswapCoinSecretKey(), + ); } /** @@ -159,11 +168,16 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @returns As per the IERC20 spec, this MUST return true. */ public transferFrom( - fromAddress: Either, - to: Either, + fromAddress: Either, + to: Either, value: bigint, ): boolean { - return this.circuits.impure.transferFrom(fromAddress, to, value); + return this.circuits.impure.transferFrom( + fromAddress, + to, + value, + this.privateState.getCurrentZswapCoinSecretKey(), + ); } /** @@ -174,11 +188,16 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @returns As per the IERC20 spec, this MUST return true. */ public _unsafeTransferFrom( - fromAddress: Either, - to: Either, + fromAddress: Either, + to: Either, value: bigint, ): boolean { - return this.circuits.impure._unsafeTransferFrom(fromAddress, to, value); + return this.circuits.impure._unsafeTransferFrom( + fromAddress, + to, + value, + this.privateState.getCurrentZswapCoinSecretKey(), + ); } /** @@ -188,10 +207,14 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @returns Returns a boolean value indicating whether the operation succeeded. */ public approve( - spender: Either, + spender: Either, value: bigint, ): boolean { - return this.circuits.impure.approve(spender, value); + return this.circuits.impure.approve( + spender, + value, + this.privateState.getCurrentZswapCoinSecretKey(), + ); } /// @@ -207,8 +230,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param value The amount of tokens to transfer. */ public _transfer( - fromAddress: Either, - to: Either, + fromAddress: Either, + to: Either, value: bigint, ) { this.circuits.impure._transfer(fromAddress, to, value); @@ -221,8 +244,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param value The amount of tokens to transfer. */ public _unsafeUncheckedTransfer( - fromAddress: Either, - to: Either, + fromAddress: Either, + to: Either, value: bigint, ) { this.circuits.impure._unsafeUncheckedTransfer(fromAddress, to, value); @@ -234,7 +257,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param account The recipient of tokens minted. * @param value The amount of tokens minted. */ - public _mint(account: Either, value: bigint) { + public _mint(account: Either, value: bigint) { this.circuits.impure._mint(account, value); } @@ -244,7 +267,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param value The amount of tokens minted. */ public _unsafeMint( - account: Either, + account: Either, value: bigint, ) { this.circuits.impure._unsafeMint(account, value); @@ -256,7 +279,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param account The target owner of tokens to burn. * @param value The amount of tokens to burn. */ - public _burn(account: Either, value: bigint) { + public _burn(account: Either, value: bigint) { this.circuits.impure._burn(account, value); } @@ -269,8 +292,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param value The amount of tokens `spender` may spend on behalf of `owner`. */ public _approve( - owner: Either, - spender: Either, + owner: Either, + spender: Either, value: bigint, ) { this.circuits.impure._approve(owner, spender, value); @@ -284,44 +307,43 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param value The amount of token allowance to spend. */ public _spendAllowance( - owner: Either, - spender: Either, + owner: Either, + spender: Either, value: bigint, ) { this.circuits.impure._spendAllowance(owner, spender, value); } /** - * @description Computes an account identifier without on-chain state, allowing a user to derive - * their identity commitment before submitting it in a grant or revoke operation. + * @description Derives a Zswap coin public key from a Zswap coin secret key. * @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value. - * @returns {Bytes<32>} accountId - The computed account identifier. + * @returns {ZswapCoinPublicKey} publicKey - The derived Zswap coin public key. */ - public computeAccountId(secretKey: Uint8Array): Uint8Array { - return this.circuits.pure.computeAccountId(secretKey); + public deriveZswapCoinPublicKey(secretKey: Uint8Array): ZswapCoinPublicKey { + return this.circuits.pure.deriveZswapCoinPublicKey(secretKey); } public readonly privateState = { /** - * @description Replaces the secret key in the private state. Used in tests to + * @description Replaces the Zswap coin secret key in the private state. Used in tests to * simulate switching between different user identities or injecting incorrect * keys to test failure paths. * @param newSK - The new secret key to set. * @returns The updated private state. */ - injectSecretKey: (newSK: Uint8Array): FungibleTokenPrivateState => { + injectZswapCoinSecretKey: (newSK: Uint8Array): FungibleTokenPrivateState => { const updatedState = FungibleTokenPrivateState.withSecretKey(newSK); this.circuitContextManager.updatePrivateState(updatedState); return updatedState; }, /** - * @description Returns the current secret key from the private state. + * @description Returns the current Zswap coin secret key from the private state. * @returns The secret key. * @throws If the secret key is undefined. */ - getCurrentSecretKey: (): Uint8Array => { - const sk = this.getPrivateState().secretKey; + getCurrentZswapCoinSecretKey: (): Uint8Array => { + const sk = this.getPrivateState().zswapCoinSecretKey; if (typeof sk === 'undefined') { throw new Error('Missing secret key'); } diff --git a/contracts/src/token/witnesses/FungibleTokenWitnesses.ts b/contracts/src/token/witnesses/FungibleTokenWitnesses.ts index f1cc7185..f1fa3be3 100644 --- a/contracts/src/token/witnesses/FungibleTokenWitnesses.ts +++ b/contracts/src/token/witnesses/FungibleTokenWitnesses.ts @@ -2,27 +2,13 @@ // OpenZeppelin Compact Contracts v0.0.1-alpha.1 (token/witnesses/FungibleTokenWitnesses.ts) import { getRandomValues } from 'node:crypto'; -import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; /** - * @description Interface defining the witness methods for FungibleToken operations. - * @template P - The private state type. - */ -export interface IFungibleTokenWitnesses { - /** - * Retrieves the secret key from the private state. - * @param context - The witness context containing the private state. - * @returns A tuple of the private state and the secret key as a Uint8Array. - */ - wit_FungibleTokenSK(context: WitnessContext): [P, Uint8Array]; -} - -/** - * @description Represents the private state of a FungibleToken contract, storing a secret key. + * @description Represents the private state of a FungibleToken contract, storing a Zswap coin secret key. */ export type FungibleTokenPrivateState = { - /** @description A 32-byte secret key used for creating a public user identifier. */ - secretKey: Uint8Array; + /** @description A 32-byte secret key used for deriving a Zswap coin public key. */ + zswapCoinSecretKey: Uint8Array; }; /** @@ -30,26 +16,26 @@ export type FungibleTokenPrivateState = { */ export const FungibleTokenPrivateState = { /** - * @description Generates a new private state with a random secret key. + * @description Generates a new private state with a random Zswap coin secret key. * @returns A fresh FungibleTokenPrivateState instance. */ generate: (): FungibleTokenPrivateState => { - return { secretKey: getRandomValues(new Uint8Array(32)) }; + return { zswapCoinSecretKey: getRandomValues(new Uint8Array(32)) }; }, /** - * @description Generates a new private state with a user-defined secret key. + * @description Generates a new private state with a user-defined Zswap coin secret key. * Useful for deterministic key generation or advanced use cases. * * @param sk - The 32-byte secret key to use. * @returns A fresh FungibleTokenPrivateState instance with the provided key. * - * @example - * ```typescript - * // For deterministic keys (user-defined scheme) - * const deterministicKey = myDeterministicScheme(...); - * const privateState = FungibleTokenPrivateState.withSecretKey(deterministicKey); - * ``` + * @example + * ```typescript + * // For deterministic keys (user-defined scheme) + * const deterministicKey = myDeterministicScheme(...); + * const privateState = FungibleTokenPrivateState.withSecretKey(deterministicKey); + * ``` */ withSecretKey: (sk: Uint8Array): FungibleTokenPrivateState => { if (sk.length !== 32) { @@ -57,24 +43,14 @@ export const FungibleTokenPrivateState = { `withSecretKey: expected 32-byte secret key, received ${sk.length} bytes`, ); } - return { secretKey: Uint8Array.from(sk) }; + return { zswapCoinSecretKey: Uint8Array.from(sk) }; }, }; /** - * @description Factory function creating witness implementations for FungibleToken operations. - * @returns An object implementing the Witnesses interface for FungibleTokenPrivateState. + * @description Factory function creating the witness object for FungibleToken simulation. + * Caller authorization is provided through explicit circuit inputs, so no token-specific + * witness methods are required. + * @returns An empty witness object for FungibleTokenPrivateState. */ -export const FungibleTokenWitnesses = (): IFungibleTokenWitnesses< - L, - FungibleTokenPrivateState -> => ({ - wit_FungibleTokenSK( - context: WitnessContext, - ): [FungibleTokenPrivateState, Uint8Array] { - return [ - context.privateState, - Uint8Array.from(context.privateState.secretKey), - ]; - }, -}); +export const FungibleTokenWitnesses = () => ({}); diff --git a/contracts/src/token/witnesses/test/FungibleTokenWitnesses.test.ts b/contracts/src/token/witnesses/test/FungibleTokenWitnesses.test.ts index b987ca03..3f3af947 100644 --- a/contracts/src/token/witnesses/test/FungibleTokenWitnesses.test.ts +++ b/contracts/src/token/witnesses/test/FungibleTokenWitnesses.test.ts @@ -1,6 +1,4 @@ -import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; import { describe, expect, it } from 'vitest'; -import type { Ledger } from '../../../../artifacts/MockFungibleToken/contract/index.js'; import { FungibleTokenPrivateState, FungibleTokenWitnesses, @@ -10,23 +8,23 @@ const SECRET_KEY = new Uint8Array(32).fill(0x34); describe('FungibleTokenPrivateState', () => { describe('generate', () => { - it('should return a state with a 32-byte secretKey', () => { + it('should return a state with a 32-byte zswapCoinSecretKey', () => { const state = FungibleTokenPrivateState.generate(); - expect(state.secretKey).toBeInstanceOf(Uint8Array); - expect(state.secretKey.length).toBe(32); + expect(state.zswapCoinSecretKey).toBeInstanceOf(Uint8Array); + expect(state.zswapCoinSecretKey.length).toBe(32); }); it('should produce unique secret key on successive calls', () => { const a = FungibleTokenPrivateState.generate(); const b = FungibleTokenPrivateState.generate(); - expect(a.secretKey).not.toEqual(b.secretKey); + expect(a.zswapCoinSecretKey).not.toEqual(b.zswapCoinSecretKey); }); }); describe('withSecretKey', () => { it('should accept a valid 32-byte secret key', () => { const state = FungibleTokenPrivateState.withSecretKey(SECRET_KEY); - expect(state.secretKey).toEqual(SECRET_KEY); + expect(state.zswapCoinSecretKey).toEqual(SECRET_KEY); }); it('should create a defensive copy of the input secret key', () => { @@ -34,7 +32,7 @@ describe('FungibleTokenPrivateState', () => { const state = FungibleTokenPrivateState.withSecretKey(sk); sk.fill(0xff); - expect(state.secretKey).toEqual(new Uint8Array(32).fill(0xcc)); + expect(state.zswapCoinSecretKey).toEqual(new Uint8Array(32).fill(0xcc)); }); it('should throw for a secret key shorter than 32 bytes', () => { @@ -64,52 +62,8 @@ describe('FungibleTokenPrivateState', () => { describe('FungibleTokenWitnesses', () => { const witnesses = FungibleTokenWitnesses(); - function makeContext( - privateState: FungibleTokenPrivateState, - ): WitnessContext { - return { privateState } as WitnessContext< - Ledger, - FungibleTokenPrivateState - >; - } - - describe('wit_FungibleTokenSK', () => { - it('should return a tuple of [privateState, secretKey]', () => { - const state = FungibleTokenPrivateState.withSecretKey(SECRET_KEY); - const ctx = makeContext(state); - - const [returnedState, returnedSK] = witnesses.wit_FungibleTokenSK(ctx); - - expect(returnedState).toBe(state); - expect(returnedSK).toEqual(SECRET_KEY); - }); - - it('should return the exact same privateState reference', () => { - const state = FungibleTokenPrivateState.generate(); - const ctx = makeContext(state); - - const [returnedState] = witnesses.wit_FungibleTokenSK(ctx); - expect(returnedState).toBe(state); - }); - - it('should return the secretKey as a Uint8Array', () => { - const state = FungibleTokenPrivateState.generate(); - const ctx = makeContext(state); - - const [, returnedSK] = witnesses.wit_FungibleTokenSK(ctx); - expect(returnedSK).toBeInstanceOf(Uint8Array); - expect(returnedSK.length).toBe(32); - }); - - it('should work with a randomly generated state', () => { - const state = FungibleTokenPrivateState.generate(); - const ctx = makeContext(state); - - const [returnedState, returnedSK] = witnesses.wit_FungibleTokenSK(ctx); - - expect(returnedState).toBe(state); - expect(returnedSK).toEqual(state.secretKey); - }); + it('should expose no caller-auth witnesses', () => { + expect(witnesses).toEqual({}); }); }); @@ -123,16 +77,6 @@ describe('FungibleTokenWitnesses factory', () => { it('should produce witnesses with identical behaviour', () => { const a = FungibleTokenWitnesses(); const b = FungibleTokenWitnesses(); - const state = FungibleTokenPrivateState.generate(); - const ctx = { privateState: state } as WitnessContext< - Ledger, - FungibleTokenPrivateState - >; - - const [stateA, skA] = a.wit_FungibleTokenSK(ctx); - const [stateB, skB] = b.wit_FungibleTokenSK(ctx); - - expect(stateA).toBe(stateB); - expect(skA).toEqual(skB); + expect(a).toEqual(b); }); }); diff --git a/package.json b/package.json index 70be91bc..9de85418 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "glob": "~10.5.0" }, "dependencies": { - "@midnight-ntwrk/compact-runtime": "0.14.0" + "@midnight-ntwrk/compact-runtime": "0.16.0" }, "devDependencies": { "@biomejs/biome": "^2.4.15", diff --git a/packages/compact/README.md b/packages/compact/README.md index e0e85cd7..3452aa52 100644 --- a/packages/compact/README.md +++ b/packages/compact/README.md @@ -11,7 +11,7 @@ Verify your Compact installation: ```bash $ compact compile --version -Compactc version: 0.29.0 +Compactc version: 0.31.0 ``` ## Binaries @@ -37,7 +37,7 @@ compact-compiler [options] |--------|-------------|---------| | `--dir ` | Compile specific subdirectory within src | (all) | | `--skip-zk` | Skip zero-knowledge proof generation | `false` | -| `+` | Use specific toolchain version (e.g., `+0.29.0`) | (default) | +| `+` | Use specific toolchain version (e.g., `+0.31.0`) | (default) | ### Environment Variables @@ -74,7 +74,7 @@ compact-compiler --dir security compact-compiler --skip-zk # Use specific toolchain version -compact-compiler +0.29.0 +compact-compiler +0.31.0 # Combine options compact-compiler --dir access --skip-zk @@ -122,7 +122,7 @@ import { CompactCompiler } from '@openzeppelin-compact/compact'; const compiler = new CompactCompiler( '--skip-zk', 'security', - '0.29.0', + '0.31.0', ); await compiler.compile(); @@ -131,7 +131,7 @@ await compiler.compile(); const compiler = CompactCompiler.fromArgs([ '--dir', 'security', '--skip-zk', - '+0.29.0' + '+0.31.0' ]); await compiler.compile(); @@ -187,12 +187,12 @@ yarn clean ```bash ℹ [COMPILE] Compact compiler started ℹ [COMPILE] Compact developer tools: compact 0.4.0 -ℹ [COMPILE] Compact toolchain: Compactc version: 0.29.0 +ℹ [COMPILE] Compact toolchain: Compactc version: 0.31.0 ℹ [COMPILE] Found 2 .compact file(s) to compile ✔ [COMPILE] [1/2] Compiled AccessControl.compact - Compactc version: 0.29.0 + Compactc version: 0.31.0 ✔ [COMPILE] [2/2] Compiled Token.compact - Compactc version: 0.29.0 + Compactc version: 0.31.0 ``` ## License diff --git a/packages/compact/src/versions.ts b/packages/compact/src/versions.ts index 827db1bd..0b898979 100644 --- a/packages/compact/src/versions.ts +++ b/packages/compact/src/versions.ts @@ -1,2 +1,2 @@ -export const COMPACT_VERSION = '0.29.0'; +export const COMPACT_VERSION = '0.31.0'; export const LANGUAGE_VERSION = '0.21.0'; diff --git a/packages/simulator/package.json b/packages/simulator/package.json index d38a0d47..0a3d840c 100644 --- a/packages/simulator/package.json +++ b/packages/simulator/package.json @@ -38,6 +38,6 @@ "vitest": "^4.1.6" }, "dependencies": { - "@midnight-ntwrk/compact-runtime": "0.14.0" + "@midnight-ntwrk/compact-runtime": "0.16.0" } } diff --git a/yarn.lock b/yarn.lock index 655d6c95..d18e4a90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -180,14 +180,14 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/compact-runtime@npm:0.14.0": - version: 0.14.0 - resolution: "@midnight-ntwrk/compact-runtime@npm:0.14.0" +"@midnight-ntwrk/compact-runtime@npm:0.16.0": + version: 0.16.0 + resolution: "@midnight-ntwrk/compact-runtime@npm:0.16.0" dependencies: - "@midnight-ntwrk/onchain-runtime-v2": "npm:^2.0.0" + "@midnight-ntwrk/onchain-runtime-v3": "npm:^3.0.0" "@types/object-inspect": "npm:^1.8.1" object-inspect: "npm:^1.12.3" - checksum: 10/bba44d09770b172b7a5ba193f59d2ec57ca0dff2e3fd538326942e102e8cbe0b0cc1cb736e1f469afc74258517e7d25fc4dfa7f89a299aed900efc89f1eed3a7 + checksum: 10/ef0c68d53bba6a04f336094c82c26b781082d7ce4ee09f0539009fb108776b36ea24b9a774292d9bbf9722b8a78d47254b5f80a613d4010a7f7d108514243023 languageName: node linkType: hard @@ -198,10 +198,17 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/onchain-runtime-v2@npm:^2.0.0": - version: 2.0.0 - resolution: "@midnight-ntwrk/onchain-runtime-v2@npm:2.0.0" - checksum: 10/71b2b5e2270ce36fbdb63c0bd531f09f2de9151b286b6c7389966279750080b300893aef973621e438a934f9274277181cdf9bdc1350abc0e244fa892a145b19 +"@midnight-ntwrk/ledger-v8@npm:8.0.3": + version: 8.0.3 + resolution: "@midnight-ntwrk/ledger-v8@npm:8.0.3" + checksum: 10/93d24ddeff967a5f5d566a7e8fc0c5586f309e954adf56761fff4ab67874b846c2a4f3f2aede4f51a9e1445d01f52a7446da121473f0120793bc622feeeed207 + languageName: node + linkType: hard + +"@midnight-ntwrk/onchain-runtime-v3@npm:^3.0.0": + version: 3.0.0 + resolution: "@midnight-ntwrk/onchain-runtime-v3@npm:3.0.0" + checksum: 10/873aeb9e631c3678373c62b5aef847de454de94427028fb3d3f28bfdc8b2c02a3c770bd79d9bfef183eb9db6fb8c23e6826636f2e512ffd6eacbcf7cc0651c5d languageName: node linkType: hard @@ -266,7 +273,7 @@ __metadata: version: 0.0.0-use.local resolution: "@openzeppelin-compact/contracts-simulator@workspace:packages/simulator" dependencies: - "@midnight-ntwrk/compact-runtime": "npm:0.14.0" + "@midnight-ntwrk/compact-runtime": "npm:0.16.0" "@midnight-ntwrk/ledger-v7": "npm:7.0.3" "@midnight-ntwrk/zswap": "npm:^4.0.0" "@tsconfig/node24": "npm:^24.0.4" @@ -281,6 +288,7 @@ __metadata: version: 0.0.0-use.local resolution: "@openzeppelin/compact-contracts@workspace:contracts" dependencies: + "@midnight-ntwrk/ledger-v8": "npm:8.0.3" "@openzeppelin-compact/compact": "workspace:^" "@openzeppelin-compact/contracts-simulator": "workspace:^" "@tsconfig/node24": "npm:^24.0.4" @@ -1449,7 +1457,7 @@ __metadata: resolution: "openzeppelin-compact@workspace:." dependencies: "@biomejs/biome": "npm:^2.4.15" - "@midnight-ntwrk/compact-runtime": "npm:0.14.0" + "@midnight-ntwrk/compact-runtime": "npm:0.16.0" "@midnight-ntwrk/ledger-v7": "npm:7.0.3" "@midnight-ntwrk/zswap": "npm:^4.0.0" "@types/node": "npm:25.7.0"