diff --git a/content/contracts-compact/accessControl.mdx b/content/contracts-compact/access-control.mdx
similarity index 67%
rename from content/contracts-compact/accessControl.mdx
rename to content/contracts-compact/access-control.mdx
index b7f4275f..846282ee 100644
--- a/content/contracts-compact/accessControl.mdx
+++ b/content/contracts-compact/access-control.mdx
@@ -6,17 +6,19 @@ title: AccessControl
[Role-Based Access Control (RBAC)]: https://en.wikipedia.org/wiki/Role-based_access_control
[principle of least privilege]: https://en.wikipedia.org/wiki/Principle_of_least_privilege
-[FungibleToken]: ./fungibleToken.mdx
+[FungibleToken]: ./fungible-token.mdx
[Ownable]: ./ownable.mdx
[Initializable]: ./security#initializable
-[AccessControl]: api/accessControl
+[AccessControl]: api/access-control
+[ShieldedAccessControl]: ./shielded-access-control.mdx
-[assertOnlyRole]: api/accessControl#AccessControl-assertOnlyRole
-[grantRole]: api/accessControl#AccessControl-grantRole
-[_grantRole]: api/accessControl#AccessControl-_grantRole
-[_unsafeGrantRole]: api/accessControl#AccessControl-_unsafeGrantRole
-[revokeRole]: api/accessControl#AccessControl-revokeRole
-[_setRoleAdmin]: api/accessControl#AccessControl-_setRoleAdmin
+[assertOnlyRole]: api/access-control#AccessControl-assertOnlyRole
+[grantRole]: api/access-control#AccessControl-grantRole
+[_grantRole]: api/access-control#AccessControl-_grantRole
+[_unsafeGrantRole]: api/access-control#AccessControl-_unsafeGrantRole
+[revokeRole]: api/access-control#AccessControl-revokeRole
+[_setRoleAdmin]: api/access-control#AccessControl-_setRoleAdmin
+[computeAccountId]: api/access-control#AccessControl-computeAccountId
This module provides a role-based access control mechanism,
where roles can be used to represent a set of permissions providing the flexibility to create different levels of account authorization.
@@ -24,6 +26,10 @@ where roles can be used to represent a set of permissions providing the flexibil
Roles can be enforced using the [assertOnlyRole] circuit.
Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.
+
+ For a privacy-preserving variant that hides role holder identities on-chain, see [ShieldedAccessControl].
+
+
## Role-Based Access Control
While the simplicity of _ownership_ can be useful for simple systems or quick prototyping,
@@ -40,22 +46,34 @@ Separately, you will be able to define rules for how accounts can be granted a r
Most software uses access control systems that are role-based:
some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges.
+### Identity Model
+
+AccessControl uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it via the `wit_AccessControlSK` witness. The module computes an account identifier
+as `persistentHash(secretKey)`, which is a commitment that hides the key while providing a stable,
+pseudonymous on-chain identity.
+
+The same secret key produces the same identity across all contracts (analogous to Solidity's `msg.sender`).
+Users who desire cross-contract unlinkability can use different secret keys per contract at the wallet layer.
+
+To derive an account identifier off-chain (e.g. before passing it to a grant operation), use [computeAccountId].
+
### Using `AccessControl`
The Compact contracts library provides `AccessControl` for implementing role-based access control.
Its usage is straightforward: for each role that you want to define,
you will create a new role identifier that is used to grant, revoke, and check if an account has that role.
-Here’s a simple example of using `AccessControl` with [FungibleToken] to define a 'minter' role,
+Here's a simple example of using `AccessControl` with [FungibleToken] to define a 'minter' role,
which allows accounts that have this role to create new tokens:
```ts
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl"
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
prefix AccessControl_;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
export sealed ledger MINTER_ROLE: Bytes<32>;
@@ -67,14 +85,14 @@ constructor(
name: Opaque<"string">,
symbol: Opaque<"string">,
decimals: Uint<8>,
- minter: Either
+ minter: Either, ContractAddress>
) {
FungibleToken_initialize(name, symbol, decimals);
MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
AccessControl__grantRole(MINTER_ROLE, minter);
}
-export circuit mint(recipient: Either, value: Uint<128>): [] {
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
AccessControl_assertOnlyRole(MINTER_ROLE);
FungibleToken__mint(recipient, value);
}
@@ -85,7 +103,7 @@ Make sure you fully understand how [AccessControl] works before using it on your
or copy-pasting the examples from this guide.
-While clear and explicit, this isn’t anything we wouldn’t have been able to achieve with [Ownable].
+While clear and explicit, this isn't anything we wouldn't have been able to achieve with [Ownable].
Indeed, where `AccessControl` shines is in scenarios where granular permissions are required,
which can be implemented by defining _multiple_ roles.
@@ -96,9 +114,9 @@ Let's augment our FungibleToken example by also defining a 'burner' role, which
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl"
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
prefix AccessControl_;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
export sealed ledger MINTER_ROLE: Bytes<32>;
@@ -111,8 +129,8 @@ constructor(
name: Opaque<"string">,
symbol: Opaque<"string">,
decimals: Uint<8>,
- minter: Either,
- burner: Either
+ minter: Either, ContractAddress>,
+ burner: Either, ContractAddress>
) {
FungibleToken_initialize(name, symbol, decimals);
MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
@@ -121,12 +139,12 @@ constructor(
AccessControl__grantRole(BURNER_ROLE, burner);
}
-export circuit mint(recipient: Either, value: Uint<128>): [] {
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
AccessControl_assertOnlyRole(MINTER_ROLE);
FungibleToken__mint(recipient, value);
}
-export circuit burn(recipient: Either, value: Uint<128>): [] {
+export circuit burn(recipient: Either, ContractAddress>, value: Uint<128>): [] {
AccessControl_assertOnlyRole(BURNER_ROLE);
FungibleToken__burn(recipient, value);
}
@@ -147,13 +165,13 @@ But what if we later want to grant the 'minter' role to additional accounts?
By default, **accounts with a role cannot grant it or revoke it from other accounts**:
all having a role does is making the `hasRole` check pass.
-To grant and revoke roles dynamically, you will need help from the _role’s admin_.
+To grant and revoke roles dynamically, you will need help from the _role's admin_.
Every role has an associated admin role,
which grants permission to call the [grantRole] and [revokeRole] circuits.
A role can be granted or revoked by using these if the calling account has the corresponding admin role.
Multiple roles may have the same admin role to make management easier.
-A role’s admin can even be the same role itself,
+A role's admin can even be the same role itself,
which would cause accounts with that role to be able to also grant and revoke it.
This mechanism can be used to create complex permissioning structures resembling organizational charts,
@@ -166,15 +184,15 @@ unless [_setRoleAdmin] is used to select a new admin role.
Since it is the admin for all roles by default,
and in fact it is also its own admin, this role carries significant risk.
-Let’s take a look at the FungibleToken example, this time taking advantage of the default admin role:
+Let's take a look at the FungibleToken example, this time taking advantage of the default admin role:
```ts
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl"
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
prefix AccessControl_;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
export sealed ledger MINTER_ROLE: Bytes<32>;
@@ -187,21 +205,23 @@ constructor(
name: Opaque<"string">,
symbol: Opaque<"string">,
decimals: Uint<8>,
+ defaultAdmin: Either, ContractAddress>
) {
FungibleToken_initialize(name, symbol, decimals);
MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE"));
- // Grant the contract deployer the default admin role: it will be able
- // to grant and revoke any roles
- AccessControl__grantRole(AccessControl_DEFAULT_ADMIN_ROLE, left(ownPublicKey()));
+ // Grant the default admin role to the provided account identifier.
+ // This account will be able to grant and revoke any roles.
+ // The account identifier should be derived off-chain using computeAccountId(secretKey).
+ AccessControl__grantRole(AccessControl_DEFAULT_ADMIN_ROLE(), defaultAdmin);
}
-export circuit mint(recipient: Either, value: Uint<128>): [] {
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
AccessControl_assertOnlyRole(MINTER_ROLE);
FungibleToken__mint(recipient, value);
}
-export circuit burn(recipient: Either, value: Uint<128>): [] {
+export circuit burn(recipient: Either, ContractAddress>, value: Uint<128>): [] {
AccessControl_assertOnlyRole(BURNER_ROLE);
FungibleToken__burn(recipient, value);
}
@@ -209,9 +229,12 @@ export circuit burn(recipient: Either, valu
```
Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles.
-However, because those roles' admin role is the default admin role, and _that_ role was granted to `ownPublicKey()`,
+However, because those roles' admin role is the default admin role, and _that_ role was granted to `defaultAdmin`,
that same account can call [grantRole] to give minting or burning permission, and [revokeRole] to remove it.
+The `defaultAdmin` account identifier should be derived off-chain using `AccessControl_computeAccountId(secretKey)`,
+where `secretKey` is a 32-byte cryptographically secure random value held by the deployer.
+
Dynamic role allocation is often a desirable property,
for example in systems where trust in a participant may vary over time.
It can also be used to support use cases such as KYC,
@@ -220,7 +243,7 @@ or may be prohibitively expensive to include in a single transaction.
### Experimental features
-This module offers an experimental circuit that allow access control permissions to be granted to contract addresses [_unsafeGrantRole].
+This module offers an experimental circuit that allows access control permissions to be granted to contract addresses [_unsafeGrantRole].
Note that the circuit name is very explicit ("unsafe") with this experimental circuit.
Until contract-to-contract calls are supported,
diff --git a/content/contracts-compact/api/accessControl.mdx b/content/contracts-compact/api/access-control.mdx
similarity index 59%
rename from content/contracts-compact/api/accessControl.mdx
rename to content/contracts-compact/api/access-control.mdx
index 37d55a26..1686289b 100644
--- a/content/contracts-compact/api/accessControl.mdx
+++ b/content/contracts-compact/api/access-control.mdx
@@ -10,7 +10,7 @@ The best way to achieve this is by using `export sealed ledger` hash digests tha
```typescript
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl"
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
prefix AccessControl_;
export sealed ledger MY_ROLE: Bytes<32>;
@@ -25,19 +25,17 @@ To restrict access to a circuit, use [assertOnlyRole](#AccessControl-assertOnlyR
```typescript
circuit foo(): [] {
- assertOnlyRole(MY_ROLE);
+ AccessControl_assertOnlyRole(MY_ROLE);
}
```
Roles can be granted and revoked dynamically via the [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole) circuits.
Each role has an associated admin role,
-and only accounts that have a role’s admin role can call [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole).
+and only accounts that have a role's admin role can call [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole).
-By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`,
+By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE` (zero bytes),
which means that only accounts with this role will be able to grant or revoke other roles.
More complex role relationships can be created by using [_setRoleAdmin](#AccessControl-_setRoleAdmin).
-To set a custom `DEFAULT_ADMIN_ROLE`,
-implement the `Initializable` module and set `DEFAULT_ADMIN_ROLE` in the `initialize()` circuit.
The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role.
@@ -45,7 +43,7 @@ implement the `Initializable` module and set `DEFAULT_ADMIN_ROLE` in the `initia
- For an overview of the module, read the [AccessControl guide](../accessControl).
+ For an overview of the module, read the [AccessControl guide](../access-control).
## AccessControl [toc] [#AccessControl]
@@ -55,7 +53,7 @@ implement the `Initializable` module and set `DEFAULT_ADMIN_ROLE` in the `initia
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl";
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl";
```
---
@@ -66,7 +64,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
#### _operatorRoles [toc] [#AccessControl-_operatorRoles]
#### [!toc] [#AccessControl-_operatorRoles]
@@ -83,57 +81,83 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
Mapping from a role identifier to an admin role identifier.
-#### DEFAULT_ADMIN_ROLE [toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
-#### [!toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
-
- The default `Bytes<32>` value mimicking a constant.
-
-
### Witnesses [toc] [#AccessControl-Witnesses]
### Witnesses [!toc] [#AccessControl-Witnesses]
-None.
+#### wit_AccessControlSK [toc] [#AccessControl-wit_AccessControlSK]
+#### [!toc] [#AccessControl-wit_AccessControlSK]
+
+ 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.
+
### Circuits [toc] [#AccessControl-Circuits]
### Circuits [!toc] [#AccessControl-Circuits]
+#### DEFAULT_ADMIN_ROLE [toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
+#### [!toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
+
+ The default admin role for all roles. Returns zero bytes (`default>`).
+ Only accounts with this role will be able to grant or revoke other roles
+ unless custom admin roles are created via [_setRoleAdmin](#AccessControl-_setRoleAdmin).
+
+
#### hasRole [toc] [#AccessControl-hasRole]
#### [!toc] [#AccessControl-hasRole]
Returns `true` if `account` has been granted `roleId`.
+#### _hasRole [toc] [#AccessControl-_hasRole]
+#### [!toc] [#AccessControl-_hasRole]
+
+ Returns `true` if `account` has been granted `roleId`.
+ Internal variant that assumes `account` has already been canonicalized by the caller.
+ External consumers should use `hasRole` which canonicalizes the input automatically.
+
+
#### assertOnlyRole [toc] [#AccessControl-assertOnlyRole]
#### [!toc] [#AccessControl-assertOnlyRole]
- Reverts if caller is missing `roleId`.
+ Reverts if the caller cannot prove ownership of `roleId`.
+ The caller's identity is derived from the `wit_AccessControlSK` witness as `persistentHash(secretKey)`.
Requirements:
* The caller must have `roleId`.
- * The caller must not be a `ContractAddress`.
#### _checkRole [toc] [#AccessControl-_checkRole]
#### [!toc] [#AccessControl-_checkRole]
Reverts if `account` is missing `roleId`.
@@ -148,10 +172,10 @@ None.
- Returns the admin role that controls `roleId` or a byte array with all zero bytes if `roleId` doesn’t exist.
+ Returns the admin role that controls `roleId` or a byte array with all zero bytes if `roleId` doesn't exist.
See [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole).
To change a role's admin use [_setRoleAdmin](#AccessControl-_setRoleAdmin).
@@ -160,9 +184,9 @@ None.
#### grantRole [toc] [#AccessControl-grantRole]
#### [!toc] [#AccessControl-grantRole]
Grants `roleId` to `account`.
@@ -175,39 +199,43 @@ None.
Requirements:
* `account` must not be a ContractAddress.
- * The caller must have `roleId`’s admin role.
+ * The caller must have `roleId`'s admin role.
#### revokeRole [toc] [#AccessControl-revokeRole]
#### [!toc] [#AccessControl-revokeRole]
Revokes `roleId` from `account`.
Requirements:
- * The caller must have `roleId`’s admin role.
+ * The caller must have `roleId`'s admin role.
#### renounceRole [toc] [#AccessControl-renounceRole]
#### [!toc] [#AccessControl-renounceRole]
Revokes `roleId` from the calling account.
Roles are often managed via [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole):
- this circuit’s purpose is to provide a mechanism for accounts to lose their privileges
+ this circuit's purpose is to provide a mechanism for accounts to lose their privileges
if they are compromised (such as when a trusted device is misplaced).
+ The caller's identity is derived from the `wit_AccessControlSK` witness as
+ `persistentHash(secretKey)`. The `callerConfirmation` parameter must match
+ the caller's computed account identifier to prevent accidental renunciation.
+
We do not provide functionality for smart contracts to renounce roles because self-executing transactions are not supported on Midnight at this time.
We may revisit this in future if this feature is made available in Compact.
@@ -215,7 +243,7 @@ None.
Requirements:
- * The caller must be `callerConfirmation`.
+ * The caller's computed account identifier must match `callerConfirmation`.
* The caller must not be a `ContractAddress`.
@@ -224,18 +252,18 @@ None.
- Sets `adminRole` as `roleId`’s admin role.
+ Sets `adminRole` as `roleId`'s admin role.
#### _grantRole [toc] [#AccessControl-_grantRole]
#### [!toc] [#AccessControl-_grantRole]
Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
@@ -255,10 +283,9 @@ None.
#### _unsafeGrantRole [toc] [#AccessControl-_unsafeGrantRole]
#### [!toc] [#AccessControl-_unsafeGrantRole]
Unsafe variant of [_grantRole](#AccessControl-_grantRole).
@@ -273,12 +300,48 @@ None.
#### _revokeRole [toc] [#AccessControl-_revokeRole]
#### [!toc] [#AccessControl-_revokeRole]
Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked.
Internal circuit without access restriction.
+
+#### _computeAccountId [toc] [#AccessControl-_computeAccountId]
+#### [!toc] [#AccessControl-_computeAccountId]
+
+ Computes the caller's account identifier from the `wit_AccessControlSK` witness.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+ The result is a 32-byte commitment that uniquely identifies the caller.
+
+
+#### computeAccountId [toc] [#AccessControl-computeAccountId]
+#### [!toc] [#AccessControl-computeAccountId]
+
+ 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.
+ This is the off-chain counterpart to the internal `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+
+ The `secretKey` parameter is a sensitive secret. Mishandling it can permanently compromise the privacy guarantees of this system.
+ Never log or persist the key in plaintext. Use cryptographically secure randomness to generate keys.
+ Treat key loss as identity loss.
+ A lost key cannot be recovered.
+
+
diff --git a/content/contracts-compact/api/fungibleToken.mdx b/content/contracts-compact/api/fungible-token.mdx
similarity index 64%
rename from content/contracts-compact/api/fungibleToken.mdx
rename to content/contracts-compact/api/fungible-token.mdx
index 3778a0c0..b458cd81 100644
--- a/content/contracts-compact/api/fungibleToken.mdx
+++ b/content/contracts-compact/api/fungible-token.mdx
@@ -5,7 +5,7 @@ title: FungibleToken API
This module provides the full FungibleToken module API.
- For an overview of the module, read the [FungibleToken guide](../fungibleToken).
+ For an overview of the module, read the [FungibleToken guide](../fungible-token).
## FungibleToken [toc] [#FungibleToken]
@@ -15,7 +15,7 @@ This module provides the full FungibleToken module API.
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken";
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken";
```
---
@@ -26,7 +26,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _balances [toc] [#FungibleToken-_balances]
#### [!toc] [#FungibleToken-_balances]
@@ -36,7 +36,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _allowances [toc] [#FungibleToken-_allowances]
#### [!toc] [#FungibleToken-_allowances]
@@ -86,23 +86,49 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
### Witnesses [toc] [#FungibleToken-Witnesses]
### Witnesses [!toc] [#FungibleToken-Witnesses]
-None.
+#### wit_FungibleTokenSK [toc] [#FungibleToken-wit_FungibleTokenSK]
+#### [!toc] [#FungibleToken-wit_FungibleTokenSK]
+
+ 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.
+
### Circuits [toc] [#FungibleToken-Circuits]
### Circuits [!toc] [#FungibleToken-Circuits]
+#### ZERO [toc] [#FungibleToken-ZERO]
+#### [!toc] [#FungibleToken-ZERO]
+
+ Returns a canonical zero `Either` value for `Bytes<32>` and `ContractAddress`.
+ Returns the left variant (`Bytes<32>`) to avoid misleading contract-to-contract error messages.
+
+
#### initialize [toc] [#FungibleToken-initialize]
#### [!toc] [#FungibleToken-initialize]
Initializes the contract by setting the name, symbol, and decimals.
- This MUST be called in the implementing contract’s constructor.
+ This MUST be called in the implementing contract's constructor.
Failure to do so can lead to an irreparable contract.
Requirements:
@@ -115,7 +141,7 @@ None.
Returns the token name.
@@ -130,7 +156,7 @@ None.
Returns the symbol of the token.
@@ -145,7 +171,7 @@ None.
Returns the number of decimals used to get its user representation.
@@ -160,7 +186,7 @@ None.
Returns the value of tokens in existence.
@@ -173,9 +199,9 @@ None.
#### balanceOf [toc] [#FungibleToken-balanceOf]
#### [!toc] [#FungibleToken-balanceOf]
Returns the value of tokens owned by `account`.
@@ -188,12 +214,12 @@ None.
#### transfer [toc] [#FungibleToken-transfer]
#### [!toc] [#FungibleToken-transfer]
- Moves a `value` amount of tokens from the caller’s account to `to`.
+ Moves a `value` amount of tokens from the caller's account to `to`.
Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
@@ -211,9 +237,9 @@ None.
#### _unsafeTransfer [toc] [#FungibleToken-_unsafeTransfer]
#### [!toc] [#FungibleToken-_unsafeTransfer]
Unsafe variant of [transfer](#FungibleToken-transfer) which allows transfers to contract addresses.
@@ -234,14 +260,11 @@ None.
#### allowance [toc] [#FungibleToken-allowance]
#### [!toc] [#FungibleToken-allowance]
Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through [transferFrom](#FungibleToken-transferFrom).
This value changes when [approve](#FungibleToken-approve) or [transferFrom](#FungibleToken-transferFrom) are called.
@@ -254,12 +277,12 @@ None.
#### approve [toc] [#FungibleToken-approve]
#### [!toc] [#FungibleToken-approve]
- Sets a `value` amount of tokens as allowance of `spender` over the caller’s tokens.
+ Sets a `value` amount of tokens as allowance of `spender` over the caller's tokens.
Requirements:
@@ -271,17 +294,17 @@ None.
#### [!toc] [#FungibleToken-transferFrom]
- Moves `value` tokens from `from` to `to` using the allowance mechanism.
- `value` is then deducted from the caller’s allowance.
+ Moves `value` tokens from `fromAddress` to `to` using the allowance mechanism.
+ `value` is then deducted from the caller's allowance.
Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
@@ -291,23 +314,23 @@ None.
Requirements:
* Contract is initialized.
- * `from` is not the zero address.
- * `from` must have a balance of at least `value`.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have a balance of at least `value`.
* `to` is not the zero address.
* `to` is not a ContractAddress.
- * The caller has an allowance of `from`’s tokens of at least `value`.
+ * The caller has an allowance of `fromAddress`'s tokens of at least `value`.
#### _unsafeTransferFrom [toc] [#FungibleToken-_unsafeTransferFrom]
#### [!toc] [#FungibleToken-_unsafeTransferFrom]
@@ -322,26 +345,26 @@ None.
Requirements:
* Contract is initialized.
- * `from` is not the zero address.
- * `from` must have a balance of at least `value`.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have a balance of at least `value`.
* `to` is not the zero address.
- * The caller has an allowance of `from`’s tokens of at least `value`.
+ * The caller has an allowance of `fromAddress`'s tokens of at least `value`.
#### _transfer [toc] [#FungibleToken-_transfer]
#### [!toc] [#FungibleToken-_transfer]
- Moves a `value` amount of tokens from `from` to `to`.
+ Moves a `value` amount of tokens from `fromAddress` to `to`.
This circuit is equivalent to [transfer](#FungibleToken-transfer), and can be used to e.g.
implement automatic token fees, slashing mechanisms, etc.
@@ -353,8 +376,8 @@ None.
Requirements:
* Contract is initialized.
- * `from` is not the zero address.
- * `from` must have at least a balance of `value`.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have at least a balance of `value`.
* `to` must not be the zero address.
* `to` must not be a ContractAddress.
@@ -363,14 +386,14 @@ None.
#### [!toc] [#FungibleToken-_unsafeUncheckedTransfer]
Unsafe variant of [_transfer](#FungibleToken-_transfer) which allows transfers to contract addresses.
@@ -383,7 +406,7 @@ None.
Requirements:
* Contract is initialized.
- * `from` is not the zero address.
+ * `fromAddress` is not the zero address.
* `to` is not the zero address.
@@ -391,17 +414,17 @@ None.
#### [!toc] [#FungibleToken-_update]
- Transfers a `value` amount of tokens from `from` to `to`,
- or alternatively mints (or burns) if `from` (or `to`) is the zero address.
+ Transfers a `value` amount of tokens from `fromAddress` to `to`,
+ or alternatively mints (or burns) if `fromAddress` (or `to`) is the zero address.
Requirements:
@@ -411,27 +434,32 @@ None.
#### _mint [toc] [#FungibleToken-_mint]
#### [!toc] [#FungibleToken-_mint]
Creates a `value` amount of tokens and assigns them to `account`, by transferring it from the zero address.
- Relies on the `update` mechanism.
+ Relies on the `_update` mechanism.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
+ This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt.
+
Requirements:
* Contract is initialized.
- * `to` is not a ContractAddress.
+ * `account` is not a ContractAddress.
* `account` is not the zero address.
#### _unsafeMint [toc] [#FungibleToken-_unsafeMint]
#### [!toc] [#FungibleToken-_unsafeMint]
Unsafe variant of [_mint](#FungibleToken-_mint) which allows transfers to contract addresses.
@@ -451,9 +479,9 @@ None.
#### _burn [toc] [#FungibleToken-_burn]
#### [!toc] [#FungibleToken-_burn]
Destroys a `value` amount of tokens from `account`, lowering the total supply.
@@ -470,16 +498,16 @@ None.
#### [!toc] [#FungibleToken-_approve]
- Sets `value` as the allowance of `spender` over the `owner`’s tokens.
+ Sets `value` as the allowance of `spender` over the `owner`'s tokens.
This circuit is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain subsystems, etc.
Requirements:
@@ -493,16 +521,16 @@ None.
#### [!toc] [#FungibleToken-_spendAllowance]
- Updates `owner`’s allowance for `spender` based on spent `value`.
+ Updates `owner`'s allowance for `spender` based on spent `value`.
Does not update the allowance value in case of infinite allowance.
Requirements:
@@ -510,3 +538,49 @@ None.
* Contract is initialized.
* `spender` must have at least an allowance of `value` from `owner`.
+
+#### _computeAccountId [toc] [#FungibleToken-_computeAccountId]
+#### [!toc] [#FungibleToken-_computeAccountId]
+
+ Computes the caller's account identifier from the `wit_FungibleTokenSK` witness.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+ The result is a 32-byte commitment that uniquely identifies the caller.
+
+
+#### computeAccountId [toc] [#FungibleToken-computeAccountId]
+#### [!toc] [#FungibleToken-computeAccountId]
+
+ 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 the internal `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+
+ The `secretKey` parameter is a sensitive secret. Mishandling it can permanently compromise the security of this system.
+ Never log or persist the key in plaintext. Use cryptographically secure randomness to generate keys.
+ Treat key loss as identity loss.
+ A lost key cannot be recovered.
+
+
+
+#### _isTargetZero [toc] [#FungibleToken-_isTargetZero]
+#### [!toc] [#FungibleToken-_isTargetZero]
+
+ Returns `true` if `target`'s active branch (as indicated by `is_left`) holds the zero value.
+
diff --git a/content/contracts-compact/api/multitoken.mdx b/content/contracts-compact/api/multi-token.mdx
similarity index 52%
rename from content/contracts-compact/api/multitoken.mdx
rename to content/contracts-compact/api/multi-token.mdx
index 3f0cc9e6..5444b95b 100644
--- a/content/contracts-compact/api/multitoken.mdx
+++ b/content/contracts-compact/api/multi-token.mdx
@@ -5,7 +5,7 @@ title: MultiToken API
This module provides the full MultiToken module API.
- For an overview of the module, read the [MultiToken guide](../multitoken).
+ For an overview of the module, read the [MultiToken guide](../multi-token).
## MultiToken [toc] [#MultiToken]
@@ -15,7 +15,7 @@ This module provides the full MultiToken module API.
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/MultiToken";
+import "./node_modules/@openzeppelin/token/src/MultiToken";
```
---
@@ -26,7 +26,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _balances [toc] [#MultiToken-_balances]
#### [!toc] [#MultiToken-_balances]
@@ -36,7 +36,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _operatorApprovals [toc] [#MultiToken-_operatorApprovals]
#### [!toc] [#MultiToken-_operatorApprovals]
@@ -56,22 +56,45 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
### Witnesses [toc] [#MultiToken-Witnesses]
### Witnesses [!toc] [#MultiToken-Witnesses]
-None.
+#### wit_MultiTokenSK [toc] [#MultiToken-wit_MultiTokenSK]
+#### [!toc] [#MultiToken-wit_MultiTokenSK]
+
+ 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.
+
### Circuits [toc] [#MultiToken-Circuits]
### Circuits [!toc] [#MultiToken-Circuits]
+#### ZERO [toc] [#MultiToken-ZERO]
+#### [!toc] [#MultiToken-ZERO]
+
+ Returns a canonical zero `Either` value (left variant with zero `Bytes<32>`).
+ Used as the zero value for mint/burn operations in `_update`, where a zero
+ `fromAddress` signals a mint and a zero `to` signals a burn.
+
+
#### initialize [toc] [#MultiToken-initialize]
#### [!toc] [#MultiToken-initialize]
Initializes the contract by setting the base URI for all tokens.
- This MUST be called in the implementing contract’s constructor.
+ This MUST be called in the implementing contract's constructor.
Failure to do so can lead to an irreparable contract.
Requirements:
@@ -84,12 +107,12 @@ None.
This implementation returns the same URI for **all** token types.
It relies on the token type ID substitution mechanism defined in the EIP: [ERC1155-Metadata](https://eips.ethereum.org/EIPS/eip-1155#metadata).
- Clients calling this function must replace the `\id\` substring with the actual token type ID.
+ Clients calling this function must replace the `\{id\}` substring with the actual token type ID.
Requirements:
@@ -99,10 +122,14 @@ None.
#### balanceOf [toc] [#MultiToken-balanceOf]
#### [!toc] [#MultiToken-balanceOf]
Returns the amount of `id` tokens owned by `account`.
@@ -114,30 +141,37 @@ None.
#### setApprovalForAll [toc] [#MultiToken-setApprovalForAll]
#### [!toc] [#MultiToken-setApprovalForAll]
- Enables or disables approval for `operator` to manage all of the caller’s assets.
+ Enables or disables approval for `operator` to manage all of the caller's assets.
+ The caller's identity is derived from the `wit_MultiTokenSK` witness as `persistentHash(secretKey)`.
Requirements:
* Contract is initialized.
- * `operator` is not the zero address.
+ * `operator` is not zero.
#### isApprovedForAll [toc] [#MultiToken-isApprovedForAll]
#### [!toc] [#MultiToken-isApprovedForAll]
- Queries if `operator` is an authorized operator for `owner`.
+ Queries if `operator` is an authorized operator for `account`.
Requirements:
@@ -147,14 +181,19 @@ None.
#### transferFrom [toc] [#MultiToken-transferFrom]
#### [!toc] [#MultiToken-transferFrom]
- Transfers ownership of `value` amount of `id` tokens from `from` to `to`.
- The caller must be `from` or approved to transfer on their behalf.
+ Transfers ownership of `value` amount of `id` tokens from `fromAddress` to `to`.
+ The caller must be `fromAddress` or approved to transfer on their behalf.
+ The caller's identity is derived from the `wit_MultiTokenSK` witness as `persistentHash(secretKey)`.
Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
@@ -165,22 +204,27 @@ None.
* Contract is initialized.
* `to` is not a ContractAddress.
- * `to` is not the zero address.
- * `from` is not the zero address.
- * Caller must be `from` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
- * `from` must have an `id` balance of at least `value`.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * Caller must be `fromAddress` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
+ * `fromAddress` must have an `id` balance of at least `value`.
#### _transfer [toc] [#MultiToken-_transfer]
#### [!toc] [#MultiToken-_transfer]
- Transfers ownership of `value` amount of `id` tokens from `from` to `to`.
+ Transfers ownership of `value` amount of `id` tokens from `fromAddress` to `to`.
Does not impose restrictions on the caller, making it suitable for composition in higher-level contract logic.
@@ -192,40 +236,50 @@ None.
* Contract is initialized.
* `to` is not a ContractAddress.
- * `to` is not the zero address.
- * `from` is not the zero address.
- * `from` must have an `id` balance of at least `value`.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
#### _update [toc] [#MultiToken-_update]
#### [!toc] [#MultiToken-_update]
- Transfers a value amount of tokens of type id from from to to.
- This circuit will mint (or burn) if `from` (or `to`) is the zero address.
+ Transfers a value amount of tokens of type `id` from `fromAddress` to `to`.
+ This circuit will mint (or burn) if `fromAddress` (or `to`) is zero.
Requirements:
* Contract is initialized.
- * If `from` is not zero, the balance of `id` of `from` must be >= `value`.
+ * If `fromAddress` is not zero, the balance of `id` of `fromAddress` must be >= `value`.
#### _unsafeTransferFrom [toc] [#MultiToken-_unsafeTransferFrom]
#### [!toc] [#MultiToken-_unsafeTransferFrom]
Unsafe variant of [transferFrom](#MultiToken-transferFrom) which allows transfers to contract addresses.
- The caller must be `from` or approved to transfer on their behalf.
+ The caller must be `fromAddress` or approved to transfer on their behalf.
+ The caller's identity is derived from the `wit_MultiTokenSK` witness as `persistentHash(secretKey)`.
Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported.
@@ -236,18 +290,23 @@ None.
Requirements:
* Contract is initialized.
- * `to` is not the zero address.
- * `from` is not the zero address.
- * Caller must be `from` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
- * `from` must have an `id` balance of at least `value`.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * Caller must be `fromAddress` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
+ * `fromAddress` must have an `id` balance of at least `value`.
#### _unsafeTransfer [toc] [#MultiToken-_unsafeTransfer]
#### [!toc] [#MultiToken-_unsafeTransfer]
@@ -263,9 +322,9 @@ None.
Requirements:
* Contract is initialized.
- * `from` is not the zero address.
- * `to` is not the zero address.
- * `from` must have an `id` balance of at least `value`.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
#### _setURI [toc] [#MultiToken-_setURI]
@@ -273,16 +332,16 @@ None.
Sets a new URI for all token types, by relying on the token type ID substitution mechanism defined in the MultiToken standard.
See https://eips.ethereum.org/EIPS/eip-1155#metadata.
- By this mechanism, any occurrence of the `\id\` substring
+ By this mechanism, any occurrence of the `\{id\}` substring
in either the URI or any of the values in the JSON file at said URI will be replaced by clients with the token type ID.
- For example, the `https://token-cdn-domain/\id\.json` URI would be interpreted by clients as
+ For example, the `https://token-cdn-domain/\{id\}.json` URI would be interpreted by clients as
`https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` for token type ID 0x4cce0.
Requirements:
@@ -293,15 +352,15 @@ None.
#### _mint [toc] [#MultiToken-_mint]
#### [!toc] [#MultiToken-_mint]
- Creates a `value` amount of tokens of type `token_id`, and assigns them to `to`.
+ Creates a `value` amount of tokens of type `id`, and assigns them to `to`.
-
+
Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt.
@@ -309,20 +368,24 @@ None.
Requirements:
* Contract is initialized.
- * `to` is not the zero address.
- * `to` is not a ContractAddress
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
#### _unsafeMint [toc] [#MultiToken-_unsafeMint]
#### [!toc] [#MultiToken-_unsafeMint]
- Unsafe variant of `_mint` which allows transfers to contract addresses.
+ Unsafe variant of [_mint](#MultiToken-_mint) which allows transfers to contract addresses.
Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported.
@@ -333,41 +396,95 @@ None.
Requirements:
* Contract is initialized.
- * `to` is not the zero address.
+ * `to` is not zero.
#### _burn [toc] [#MultiToken-_burn]
#### [!toc] [#MultiToken-_burn]
- Destroys a `value` amount of tokens of type `token_id` from `from`.
+ Destroys a `value` amount of tokens of type `id` from `fromAddress`.
Requirements:
* Contract is initialized.
- * `from` is not the zero address.
- * `from` must have an `id` balance of at least `value`.
+ * `fromAddress` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
#### _setApprovalForAll [toc] [#MultiToken-_setApprovalForAll]
#### [!toc] [#MultiToken-_setApprovalForAll]
- Enables or disables approval for `operator` to manage all of the caller’s assets.
+ Enables or disables approval for `operator` to manage all of `owner`'s assets.
This circuit does not check for access permissions but can be useful as a building block for more complex contract logic.
Requirements:
* Contract is initialized.
- * `operator` is not the zero address.
+ * `owner` is not zero.
+ * `operator` is not zero.
+
+
+#### _computeAccountId [toc] [#MultiToken-_computeAccountId]
+#### [!toc] [#MultiToken-_computeAccountId]
+
+ Computes the caller's account identifier from the `wit_MultiTokenSK` witness.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+ The result is a 32-byte commitment that uniquely identifies the caller.
+
+
+#### computeAccountId [toc] [#MultiToken-computeAccountId]
+#### [!toc] [#MultiToken-computeAccountId]
+
+ Computes an account identifier without on-chain state, allowing a user to derive
+ their identity commitment before submitting it in a token operation.
+ This is the off-chain counterpart to the internal `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+
+ The `secretKey` parameter is a sensitive secret. Mishandling it can permanently compromise the security of this system.
+ Never log or persist the key in plaintext. Use cryptographically secure randomness to generate keys.
+ Treat key loss as identity loss — a lost key cannot be recovered.
+
+
+
+#### _isTargetZero [toc] [#MultiToken-_isTargetZero]
+#### [!toc] [#MultiToken-_isTargetZero]
+
+ Returns `true` if `target`'s active branch (as indicated by `is_left`) holds the zero value.
diff --git a/content/contracts-compact/api/nonFungibleToken.mdx b/content/contracts-compact/api/non-fungible-token.mdx
similarity index 61%
rename from content/contracts-compact/api/nonFungibleToken.mdx
rename to content/contracts-compact/api/non-fungible-token.mdx
index ad6e7724..4bfa6409 100644
--- a/content/contracts-compact/api/nonFungibleToken.mdx
+++ b/content/contracts-compact/api/non-fungible-token.mdx
@@ -5,7 +5,7 @@ title: NonFungibleToken API
This module provides the full NonFungibleToken module API.
- For an overview of the module, read the [NonFungibleToken guide](../nonFungibleToken).
+ For an overview of the module, read the [NonFungibleToken guide](../non-fungible-token).
## NonFungibleToken [toc] [#NonFungibleToken]
@@ -15,7 +15,7 @@ This module provides the full NonFungibleToken module API.
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken";
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken";
```
---
@@ -46,7 +46,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _owners [toc] [#NonFungibleToken-_owners]
#### [!toc] [#NonFungibleToken-_owners]
@@ -56,7 +56,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _balances [toc] [#NonFungibleToken-_balances]
#### [!toc] [#NonFungibleToken-_balances]
@@ -66,7 +66,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _tokenApprovals [toc] [#NonFungibleToken-_tokenApprovals]
#### [!toc] [#NonFungibleToken-_tokenApprovals]
@@ -76,7 +76,7 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
#### _operatorApprovals [toc] [#NonFungibleToken-_operatorApprovals]
#### [!toc] [#NonFungibleToken-_operatorApprovals]
@@ -96,22 +96,44 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/tok
### Witnesses [toc] [#NonFungibleToken-Witnesses]
### Witnesses [!toc] [#NonFungibleToken-Witnesses]
-None.
+#### wit_NonFungibleTokenSK [toc] [#NonFungibleToken-wit_NonFungibleTokenSK]
+#### [!toc] [#NonFungibleToken-wit_NonFungibleTokenSK]
+
+ 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.
+
### Circuits [toc] [#NonFungibleToken-Circuits]
### Circuits [!toc] [#NonFungibleToken-Circuits]
+#### ZERO [toc] [#NonFungibleToken-ZERO]
+#### [!toc] [#NonFungibleToken-ZERO]
+
+ Returns a canonical zero `Either` value (left variant with zero `Bytes<32>`).
+ Used as the zero value for mint/burn operations and cleared approvals.
+
+
#### initialize [toc] [#NonFungibleToken-initialize]
#### [!toc] [#NonFungibleToken-initialize]
Initializes the contract by setting the name and symbol.
- This MUST be called in the implementing contract’s constructor.
+ This MUST be called in the implementing contract's constructor.
Failure to do so can lead to an irreparable contract.
Requirements:
@@ -122,9 +144,9 @@ None.
#### balanceOf [toc] [#NonFungibleToken-balanceOf]
#### [!toc] [#NonFungibleToken-balanceOf]
Returns the number of tokens in `owner`'s account.
@@ -137,9 +159,9 @@ None.
#### ownerOf [toc] [#NonFungibleToken-ownerOf]
#### [!toc] [#NonFungibleToken-ownerOf]
Returns the owner of the `tokenId` token.
@@ -155,7 +177,7 @@ None.
Returns the token name.
@@ -170,7 +192,7 @@ None.
Returns the symbol of the token.
@@ -185,7 +207,7 @@ None.
Returns the token URI for the given `tokenId`.
@@ -197,10 +219,10 @@ None.
* The `tokenId` must exist.
- Native strings and string operations aren’t supported within the Compact language,
+ Native strings and string operations aren't supported within the Compact language,
e.g. concatenating a base URI + token ID is not possible like in other NFT implementations.
Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs.
- It’s up to the implementation to decide on how to handle this.
+ It's up to the implementation to decide on how to handle this.
@@ -209,10 +231,10 @@ None.
- Sets the the URI as `tokenURI` for the given `tokenId`.
+ Sets the URI as `tokenURI` for the given `tokenId`.
Requirements:
@@ -223,15 +245,16 @@ None.
#### approve [toc] [#NonFungibleToken-approve]
#### [!toc] [#NonFungibleToken-approve]
Gives permission to `to` to transfer `tokenId` token to another account.
The approval is cleared when the token is transferred.
+ The caller's identity is derived from the `wit_NonFungibleTokenSK` witness as `persistentHash(secretKey)`.
- Only a single account can be approved at a time, so approving the zero address clears previous approvals.
+ Only a single account can be approved at a time, so approving the zero value clears previous approvals.
Requirements:
@@ -243,9 +266,9 @@ None.
#### getApproved [toc] [#NonFungibleToken-getApproved]
#### [!toc] [#NonFungibleToken-getApproved]
Returns the account approved for `tokenId` token.
@@ -259,26 +282,31 @@ None.
#### setApprovalForAll [toc] [#NonFungibleToken-setApprovalForAll]
#### [!toc] [#NonFungibleToken-setApprovalForAll]
Approve or remove `operator` as an operator for the caller.
Operators can call [transferFrom](#NonFungibleToken-transferFrom) for any token owned by the caller.
+ The caller's identity is derived from the `wit_NonFungibleTokenSK` witness as `persistentHash(secretKey)`.
Requirements:
* The contract is initialized.
- * The `operator` cannot be the zero address.
+ * The `operator` cannot be zero.
#### isApprovedForAll [toc] [#NonFungibleToken-isApprovedForAll]
#### [!toc] [#NonFungibleToken-isApprovedForAll]
@@ -286,19 +314,20 @@ None.
Requirements:
- * The contract must have been initialized.
+ * The contract is initialized.
#### transferFrom [toc] [#NonFungibleToken-transferFrom]
#### [!toc] [#NonFungibleToken-transferFrom]
- Transfers `tokenId` token from `from` to `to`.
+ Transfers `tokenId` token from `fromAddress` to `to`.
+ The caller's identity is derived from the `wit_NonFungibleTokenSK` witness as `persistentHash(secretKey)`.
Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
@@ -308,23 +337,24 @@ None.
Requirements:
* The contract is initialized.
- * `from` is not the zero address.
- * `to` is not the zero address.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
* `to` is not a ContractAddress.
- * `tokenId` token must be owned by `from`.
- * If the caller is not `from`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
+ * `tokenId` token must be owned by `fromAddress`.
+ * If the caller is not `fromAddress`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
#### _unsafeTransferFrom [toc] [#NonFungibleToken-_unsafeTransferFrom]
#### [!toc] [#NonFungibleToken-_unsafeTransferFrom]
Unsafe variant of [transferFrom](#NonFungibleToken-transferFrom) which allows transfers to contract addresses.
+ The caller's identity is derived from the `wit_NonFungibleTokenSK` witness as `persistentHash(secretKey)`.
Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported.
@@ -335,21 +365,21 @@ None.
Requirements:
* The contract is initialized.
- * `from` is not the zero address.
- * `to` is not the zero address.
- * `tokenId` token must be owned by `from`.
- * If the caller is not `from`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `tokenId` token must be owned by `fromAddress`.
+ * If the caller is not `fromAddress`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
#### _ownerOf [toc] [#NonFungibleToken-_ownerOf]
#### [!toc] [#NonFungibleToken-_ownerOf]
- Returns the owner of the `tokenId`. Does NOT revert if token doesn’t exist
+ Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist.
Requirements:
@@ -359,13 +389,13 @@ None.
#### _getApproved [toc] [#NonFungibleToken-_getApproved]
#### [!toc] [#NonFungibleToken-_getApproved]
Returns the approved address for `tokenId`.
- Returns the zero address if `tokenId` is not minted.
+ Returns the zero value if `tokenId` is not minted.
Requirements:
@@ -375,9 +405,9 @@ None.
#### _isAuthorized [toc] [#NonFungibleToken-_isAuthorized]
#### [!toc] [#NonFungibleToken-_isAuthorized]
@@ -396,9 +426,9 @@ None.
#### _checkAuthorized [toc] [#NonFungibleToken-_checkAuthorized]
#### [!toc] [#NonFungibleToken-_checkAuthorized]
@@ -417,9 +447,8 @@ None.
#### _update [toc] [#NonFungibleToken-_update]
#### [!toc] [#NonFungibleToken-_update]
@@ -437,42 +466,47 @@ None.
#### _mint [toc] [#NonFungibleToken-_mint]
#### [!toc] [#NonFungibleToken-_mint]
Mints `tokenId` and transfers it to `to`.
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
+ This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
Requirements:
* The contract is initialized.
* `tokenId` must not exist.
- * `to` is not the zero address.
+ * `to` is not zero.
* `to` is not a ContractAddress.
#### _unsafeMint [toc] [#NonFungibleToken-_unsafeMint]
#### [!toc] [#NonFungibleToken-_unsafeMint]
Unsafe variant of [_mint](#NonFungibleToken-_mint) which allows transfers to contract addresses.
- Requirements:
-
- * Contract is initialized.
- * `tokenId` must not exist.
- * `to` is not the zero address.
-
Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported.
Tokens sent to a contract address may become irretrievable.
Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `tokenId` must not exist.
+ * `to` is not zero.
#### _burn [toc] [#NonFungibleToken-_burn]
@@ -480,7 +514,7 @@ None.
Destroys `tokenId`.
@@ -496,14 +530,14 @@ None.
#### _transfer [toc] [#NonFungibleToken-_transfer]
#### [!toc] [#NonFungibleToken-_transfer]
- Transfers `tokenId` from `from` to `to`. As opposed to [transferFrom](#NonFungibleToken-transferFrom),
- this imposes no restrictions on `ownPublicKey()`.
+ Transfers `tokenId` from `fromAddress` to `to`. As opposed to [transferFrom](#NonFungibleToken-transferFrom),
+ this imposes no restrictions on the caller's identity.
Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
@@ -513,23 +547,23 @@ None.
Requirements:
* The contract is initialized.
- * `to` is not the zero address.
+ * `to` is not zero.
* `to` is not a ContractAddress.
- * `tokenId` token must be owned by `from`.
+ * `tokenId` token must be owned by `fromAddress`.
#### _unsafeTransfer [toc] [#NonFungibleToken-_unsafeTransfer]
-#### [!toc] [#NonFungibleToken-__unsafeTransfer]
+#### [!toc] [#NonFungibleToken-_unsafeTransfer]
Unsafe variant of [_transfer](#NonFungibleToken-_transfer) which allows transfers to contract addresses.
- Transfers `tokenId` from `from` to `to`. As opposed to [_unsafeTransferFrom](#NonFungibleToken-_unsafeTransferFrom), this imposes no restrictions on `ownPublicKey()`. It does NOT check if the recipient is a `ContractAddress`.
+ Transfers `tokenId` from `fromAddress` to `to`. As opposed to [_unsafeTransferFrom](#NonFungibleToken-_unsafeTransferFrom), this imposes no restrictions on the caller's identity. It does NOT check if the recipient is a `ContractAddress`.
Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported.
@@ -540,16 +574,16 @@ None.
Requirements:
* Contract is initialized.
- * `to` is not the zero address.
- * `tokenId` token must be owned by `from`.
+ * `to` is not zero.
+ * `tokenId` token must be owned by `fromAddress`.
#### _approve [toc] [#NonFungibleToken-_approve]
#### [!toc] [#NonFungibleToken-_approve]
@@ -565,29 +599,30 @@ None.
#### _setApprovalForAll [toc] [#NonFungibleToken-_setApprovalForAll]
#### [!toc] [#NonFungibleToken-_setApprovalForAll]
- Approve `operator` to operate on all of `owner` tokens
+ Approve `operator` to operate on all of `owner` tokens.
Requirements:
* The contract is initialized.
- * `operator` is not the zero address.
+ * `owner` is not zero.
+ * `operator` is not zero.
#### _requireOwned [toc] [#NonFungibleToken-_requireOwned]
#### [!toc] [#NonFungibleToken-_requireOwned]
- Reverts if the `tokenId` doesn’t have a current owner (it hasn’t been minted, or it has been burned).
+ Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
Returns the owner.
Requirements:
@@ -595,3 +630,48 @@ None.
* The contract is initialized.
* `tokenId` must exist.
+
+#### _computeAccountId [toc] [#NonFungibleToken-_computeAccountId]
+#### [!toc] [#NonFungibleToken-_computeAccountId]
+
+ Computes the caller's account identifier from the `wit_NonFungibleTokenSK` witness.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+ The result is a 32-byte commitment that uniquely identifies the caller.
+
+
+#### computeAccountId [toc] [#NonFungibleToken-computeAccountId]
+#### [!toc] [#NonFungibleToken-computeAccountId]
+
+ Computes an account identifier without on-chain state, allowing a user to derive
+ their identity commitment before submitting it in a token operation.
+ This is the off-chain counterpart to the internal `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+
+ The `secretKey` parameter is a sensitive secret. Mishandling it can permanently compromise the security of this system.
+ Never log or persist the key in plaintext. Use cryptographically secure randomness to generate keys.
+ Treat key loss as identity loss — a lost key cannot be recovered.
+
+
+
+#### _isTargetZero [toc] [#NonFungibleToken-_isTargetZero]
+#### [!toc] [#NonFungibleToken-_isTargetZero]
+
+ Returns `true` if `target`'s active branch (as indicated by `is_left`) holds the zero value.
+
diff --git a/content/contracts-compact/api/ownable.mdx b/content/contracts-compact/api/ownable.mdx
index ab892383..8d4cd78d 100644
--- a/content/contracts-compact/api/ownable.mdx
+++ b/content/contracts-compact/api/ownable.mdx
@@ -15,7 +15,7 @@ For an overview of the module, read the [Ownable guide](../ownable).
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable";
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable";
```
---
@@ -26,17 +26,28 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
#### _owner [toc] [#Ownable-_owner]
#### [!toc] [#Ownable-_owner]
- Either a `ZswapCoinPublicKey` or `ContractAddress` representing the owner.
+ Either a `Bytes<32>` account identifier or `ContractAddress` representing the owner.
### Witnesses [toc] [#Ownable-Witnesses]
### Witnesses [!toc] [#Ownable-Witnesses]
-None.
+#### wit_OwnableSK [toc] [#Ownable-wit_OwnableSK]
+#### [!toc] [#Ownable-wit_OwnableSK]
+
+ 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.
+
### Circuits [toc] [#Ownable-Circuits]
### Circuits [!toc] [#Ownable-Circuits]
@@ -44,13 +55,13 @@ None.
#### initialize [toc] [#Ownable-initialize]
#### [!toc] [#Ownable-initialize]
Initializes the contract by setting the `initialOwner`.
- This must be called in the contract’s constructor.
+ This must be called in the contract's constructor.
Requirements:
@@ -62,9 +73,9 @@ None.
#### owner [toc] [#Ownable-owner]
#### [!toc] [#Ownable-owner]
Returns the current contract owner.
@@ -77,9 +88,9 @@ None.
#### transferOwnership [toc] [#Ownable-transferOwnership]
#### [!toc] [#Ownable-transferOwnership]
Transfers ownership of the contract to `newOwner`.
@@ -100,9 +111,9 @@ None.
#### _unsafeTransferOwnership [toc] [#Ownable-_unsafeTransferOwnership]
#### [!toc] [#Ownable-_unsafeTransferOwnership]
Unsafe variant of [`transferOwnership`](#Ownable-transferOwnership).
@@ -125,7 +136,7 @@ None.
Leaves the contract without an owner.
@@ -143,11 +154,12 @@ None.
Throws if called by any account other than the owner.
Use this to restrict access of specific circuits to the owner.
+ The caller's identity is derived from the `wit_OwnableSK` witness as `persistentHash(secretKey)`.
Requirements:
@@ -158,16 +170,16 @@ None.
#### _transferOwnership [toc] [#Ownable-_transferOwnership]
#### [!toc] [#Ownable-_transferOwnership]
Transfers ownership of the contract to a `newOwner` without enforcing permission checks on the caller.
Ownership transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact.
- This restriction prevents permanently disabling access to a circuit.
+ This restriction prevents circuits from being inadvertently locked in contracts.
Requirements:
@@ -179,9 +191,9 @@ None.
#### _unsafeUncheckedTransferOwnership [toc] [#Ownable-_unsafeUncheckedTransferOwnership]
#### [!toc] [#Ownable-_unsafeUncheckedTransferOwnership]
Unsafe variant of [`_transferOwnership`](#Ownable-_transferOwnership).
@@ -197,6 +209,42 @@ None.
* Contract is initialized.
+#### _computeAccountId [toc] [#Ownable-_computeAccountId]
+#### [!toc] [#Ownable-_computeAccountId]
+
+ Computes the caller's account identifier from the `wit_OwnableSK` witness.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+ The result is a 32-byte commitment that uniquely identifies the caller.
+
+
+#### computeAccountId [toc] [#Ownable-computeAccountId]
+#### [!toc] [#Ownable-computeAccountId]
+
+ Computes an account identifier without on-chain state, allowing a user to derive
+ their identity commitment before submitting it in an ownership transfer.
+ This is the off-chain counterpart to the internal `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+ ID Derivation: `accountId = persistentHash(secretKey)`
+
+
+ The `secretKey` parameter is a sensitive secret. Mishandling it can permanently compromise the security of this system.
+ Never log or persist the key in plaintext. Use cryptographically secure randomness to generate keys.
+ Treat key loss as identity loss.
+ A lost key cannot be recovered.
+
+
+
---
## ZOwnablePK [toc] [#ZOwnablePK]
@@ -206,7 +254,7 @@ None.
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK";
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK";
```
---
@@ -285,11 +333,16 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
and storing the `instanceSalt` that acts as a privacy additive
for preventing duplicate commitments among other contracts implementing ZOwnablePK.
-
+
The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm.
Using any other algorithm will result in a permanent loss of contract access.
See [_computeOwnerId](#ZOwnablePK-_computeOwnerId).
+
+ Requirements:
+
+ * Contract is not initialized.
+ * `ownerId` is not zero.
#### owner [toc] [#ZOwnablePK-owner]
@@ -302,6 +355,10 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
>
Returns the current commitment representing the contract owner.
The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`.
+
+ Requirements:
+
+ * Contract is initialized.
#### transferOwnership [toc] [#ZOwnablePK-transferOwnership]
@@ -315,9 +372,11 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
Transfers ownership to `newOwnerId`.
`newOwnerId` must be precalculated and given to the current owner off chain.
-
- The caller must be the current owner and `newOwnerId` must not be empty.
-
+ Requirements:
+
+ * Contract is initialized.
+ * Caller is the current owner.
+ * `newOwnerId` is not an empty array.
#### renounceOwnership [toc] [#ZOwnablePK-renounceOwnership]
@@ -331,6 +390,11 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
Leaves the contract without an owner.
It will not be possible to call `assertOnlyOwner` circuits anymore.
Can only be called by the current owner.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller is the current owner.
#### assertOnlyOwner [toc] [#ZOwnablePK-assertOnlyOwner]
@@ -344,6 +408,11 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match
the stored owner commitment.
Use this to only allow the owner to call specific circuits.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller's id (`SHA256(pk, nonce)`) when used in `_computeOwnerCommitment` equals the stored `_ownerCommitment`.
#### _computeOwnerCommitment [toc] [#ZOwnablePK-_computeOwnerCommitment]
@@ -362,25 +431,32 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
Where:
- `id`: The unique identifier `SHA256(pk, nonce)`.
- `instanceSalt`: Per-deployment salt to prevent collisions.
- - `counter`: Incremented with each transfer for uniqueness.
+ - `counter`: Incremented with each transfer for uniqueness. Cast to `Field` then `Bytes<32>` for hashing.
- Domain separator: `"ZOwnablePK:shield:"` padded to 32 bytes.
+
+ Requirements:
+
+ * Contract is initialized.
#### _computeOwnerId [toc] [#ZOwnablePK-_computeOwnerId]
#### [!toc] [#ZOwnablePK-_computeOwnerId]
Computes the unique identifier (`id`) of the owner from their public key and a secret nonce.
The ID derivation follows: `id = SHA256(pk, nonce)`
-
+
Currently only supports `ZswapCoinPublicKey`. Contract address owners are not yet supported.
- We recommend using an Air-Gapped Public Key for strongest security guarantees.
+
+ Requirements:
+
+ * `pk` is not a ContractAddress.
#### _transferOwnership [toc] [#ZOwnablePK-_transferOwnership]
@@ -394,4 +470,18 @@ import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/acc
Transfers ownership to owner id `newOwnerId` without enforcing permission checks on the caller.
This is an internal function that increments the counter and updates the owner commitment.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _isTargetZero [toc] [#Ownable-_isTargetZero]
+#### [!toc] [#Ownable-_isTargetZero]
+
+ Returns `true` if `target`'s active branch (as indicated by `is_left`) holds the zero value.
diff --git a/content/contracts-compact/api/security.mdx b/content/contracts-compact/api/security.mdx
index e6f4dcd3..5920fea5 100644
--- a/content/contracts-compact/api/security.mdx
+++ b/content/contracts-compact/api/security.mdx
@@ -15,7 +15,7 @@ This package provides the API for all Security modules.
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable";
+import "./node_modules/@openzeppelin/compact-contracts/security/Initializable";
```
---
@@ -68,7 +68,7 @@ None.
Requirements:
- * Contract must not be initialized.
+ * Contract must be initialized.
#### assertNotInitialized [toc] [#Initializable-assertNotInitialized]
@@ -95,7 +95,7 @@ None.
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Pausable";
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable";
```
---
diff --git a/content/contracts-compact/api/shielded-access-control.mdx b/content/contracts-compact/api/shielded-access-control.mdx
new file mode 100644
index 00000000..d9cf7ced
--- /dev/null
+++ b/content/contracts-compact/api/shielded-access-control.mdx
@@ -0,0 +1,609 @@
+---
+title: ShieldedAccessControl API
+---
+
+This page provides the full ShieldedAccessControl module API.
+
+This module provides a shielded role-based access control (RBAC) mechanism,
+where roles can be used to represent a set of permissions.
+Roles are stored as Merkle tree commitments to avoid disclosing information about role holders.
+Role commitments are created with the following hashing scheme,
+where `‖` denotes concatenation and all values are `Bytes<32>`:
+
+```
+roleCommitment := SHA256( role ‖ accountId ‖ instanceSalt ‖ commitmentDomain )
+
+accountId := SHA256( secretKey ‖ instanceSalt ‖ accountIdDomain )
+
+roleNullifier := SHA256( roleCommitment ‖ nullifierDomain )
+
+commitmentDomain := pad(32, "ShieldedAccessControl:commitment")
+accountIdDomain := pad(32, "ShieldedAccessControl:accountId")
+nullifierDomain := pad(32, "ShieldedAccessControl:nullifier")
+```
+
+In this RBAC model, role commitments behave like private bearer tokens.
+Possession of a valid, non-revoked role commitment grants authorization.
+Revocation permanently burns the role instance, requiring explicit new issuance under a new account identifier.
+Users must rotate their secret key to generate a new `accountId` to be re-authorized.
+
+A single `secretKey` may be used for all roles within a contract instance.
+This is safe because the `role` identifier is mixed into the commitment (not the `accountId`),
+so different roles produce different commitments and different nullifiers even when the underlying `accountId` is the same.
+
+Roles are referred to by their `Bytes<32>` identifier.
+These should be exposed in the top-level contract and be unique.
+The best way to achieve this is by using `export sealed ledger` hash digests that are initialized in the top-level contract:
+
+```typescript
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl"
+ prefix ShieldedAccessControl_;
+
+export sealed ledger MY_ROLE: Bytes<32>;
+
+constructor(instanceSalt: Bytes<32>, defaultAdmin: ShieldedAccessControl_AccountIdentifier) {
+ MY_ROLE = persistentHash>(pad(32, "MY_ROLE"));
+ ShieldedAccessControl_initialize(instanceSalt);
+ ShieldedAccessControl__grantRole(ShieldedAccessControl_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+}
+```
+
+To restrict access to a circuit, use [assertOnlyRole](#ShieldedAccessControl-assertOnlyRole):
+
+```typescript
+circuit foo(): [] {
+ ShieldedAccessControl_assertOnlyRole(MY_ROLE as ShieldedAccessControl_RoleIdentifier);
+}
+```
+
+Roles can be granted and revoked dynamically via the [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole) circuits.
+Each role has an associated admin role,
+and only accounts that have a role's admin role can call [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole).
+
+By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`,
+which means that only accounts with this role will be able to grant or revoke other roles.
+More complex role relationships can be created by using [_setRoleAdmin](#ShieldedAccessControl-_setRoleAdmin).
+
+
+ The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role.
+ Extra precautions should be taken to secure accounts that have been granted it.
+
+
+
+ For an overview of the module, read the [ShieldedAccessControl guide](../shielded-access-control).
+
+
+## ShieldedAccessControl [toc] [#ShieldedAccessControl]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl"
+```
+
+---
+
+### Types [toc] [#ShieldedAccessControl-Types]
+### Types [!toc] [#ShieldedAccessControl-Types]
+
+#### UpdateType [toc] [#ShieldedAccessControl-UpdateType]
+#### [!toc] [#ShieldedAccessControl-UpdateType]
+
+ Enum indicating whether a role update is a grant or a revocation.
+
+
+#### RoleCommitment [toc] [#ShieldedAccessControl-RoleCommitment]
+#### [!toc] [#ShieldedAccessControl-RoleCommitment]
+
+ A Merkle tree leaf committing a `(role, accountId)` pairing. Computed as `SHA256(role, accountId, instanceSalt, commitmentDomain)`.
+
+
+#### RoleIdentifier [toc] [#ShieldedAccessControl-RoleIdentifier]
+#### [!toc] [#ShieldedAccessControl-RoleIdentifier]
+
+ A unique identifier for a role.
+
+
+#### AccountIdentifier [toc] [#ShieldedAccessControl-AccountIdentifier]
+#### [!toc] [#ShieldedAccessControl-AccountIdentifier]
+
+ A privacy-preserving identity commitment. Computed as `SHA256(secretKey, instanceSalt, accountIdDomain)`.
+
+
+#### RoleNullifier [toc] [#ShieldedAccessControl-RoleNullifier]
+#### [!toc] [#ShieldedAccessControl-RoleNullifier]
+
+ A one-time burn token that permanently invalidates a role commitment on revocation. Computed as `SHA256(roleCommitment, nullifierDomain)`.
+
+
+### Ledger [toc] [#ShieldedAccessControl-Ledger]
+### Ledger [!toc] [#ShieldedAccessControl-Ledger]
+
+#### _operatorRoles [toc] [#ShieldedAccessControl-_operatorRoles]
+#### [!toc] [#ShieldedAccessControl-_operatorRoles]
+
+ A Merkle tree of role commitments stored as `SHA256(role | accountId | instanceSalt | commitmentDomain)`.
+ Has a fixed capacity of 2^20 leaf slots.
+
+
+#### _adminRoles [toc] [#ShieldedAccessControl-_adminRoles]
+#### [!toc] [#ShieldedAccessControl-_adminRoles]
+
+ Mapping from a role identifier to an admin role identifier.
+
+
+#### _roleCommitmentNullifiers [toc] [#ShieldedAccessControl-_roleCommitmentNullifiers]
+#### [!toc] [#ShieldedAccessControl-_roleCommitmentNullifiers]
+
+ A set of nullifiers used to prove a role has been revoked.
+
+
+#### _instanceSalt [toc] [#ShieldedAccessControl-_instanceSalt]
+#### [!toc] [#ShieldedAccessControl-_instanceSalt]
+
+ A per-instance value provided at initialization used to namespace commitments for this contract instance.
+
+ This salt prevents commitment collisions across contracts that might otherwise use the same identifiers or domain parameters.
+ It should be a cryptographically strong random value. It is immutable after initialization.
+
+
+### Witnesses [toc] [#ShieldedAccessControl-Witnesses]
+### Witnesses [!toc] [#ShieldedAccessControl-Witnesses]
+
+#### wit_getRoleCommitmentPath [toc] [#ShieldedAccessControl-wit_getRoleCommitmentPath]
+#### [!toc] [#ShieldedAccessControl-wit_getRoleCommitmentPath]
+
+ Returns a path to a role commitment in the `_operatorRoles` Merkle tree if one exists.
+ Otherwise, returns an invalid path.
+
+
+#### wit_secretKey [toc] [#ShieldedAccessControl-wit_secretKey]
+#### [!toc] [#ShieldedAccessControl-wit_secretKey]
+
+ Returns the user's secret key used in deriving the shielded account identifier.
+
+ The same key can be used across multiple roles within a contract instance. If a role is
+ revoked and re-granted, a new secret key must be generated to produce a new `accountId`.
+
+
+### Circuits [toc] [#ShieldedAccessControl-Circuits]
+### Circuits [!toc] [#ShieldedAccessControl-Circuits]
+
+#### initialize [toc] [#ShieldedAccessControl-initialize]
+#### [!toc] [#ShieldedAccessControl-initialize]
+
+ Initializes the contract by storing the `instanceSalt` that acts as a privacy additive
+ for preventing duplicate commitments among other contracts implementing ShieldedAccessControl.
+
+
+ The `instanceSalt` must be calculated prior to contract deployment using a cryptographically
+ secure random number generator (e.g. `crypto.getRandomValues()`) to maintain strong privacy guarantees.
+
+
+ Requirements:
+
+ * Contract is not initialized.
+ * `instanceSalt` must not be zero.
+
+
+#### DEFAULT_ADMIN_ROLE [toc] [#ShieldedAccessControl-DEFAULT_ADMIN_ROLE]
+#### [!toc] [#ShieldedAccessControl-DEFAULT_ADMIN_ROLE]
+
+ The default admin role for all roles. Returns zero bytes (`default>`).
+ Only accounts with this role will be able to grant or revoke other roles
+ unless custom admin roles are created via [_setRoleAdmin](#ShieldedAccessControl-_setRoleAdmin).
+
+
+#### assertOnlyRole [toc] [#ShieldedAccessControl-assertOnlyRole]
+#### [!toc] [#ShieldedAccessControl-assertOnlyRole]
+
+ Reverts if the caller cannot provide a valid proof of ownership for `role`.
+
+ Disclosures: a Merkle tree path to a role commitment, a role commitment corresponding to a `(role, accountId)` pairing, and a nullifier for the respective role commitment.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller must prove ownership of `role`.
+ * Caller must not provide a valid Merkle tree path for a different `(role, accountId)` pairing.
+
+
+#### canProveRole [toc] [#ShieldedAccessControl-canProveRole]
+#### [!toc] [#ShieldedAccessControl-canProveRole]
+
+ Returns `true` if the caller proves ownership of `role` and is not revoked. MAY return false for a legitimately credentialed
+ caller if the proving environment supplies an invalid Merkle path. This circuit will never return true for an
+ unauthorized caller.
+
+ Disclosures: a Merkle tree path to a role commitment, a role commitment corresponding to a `(role, accountId)` pairing, and a nullifier for the respective role commitment.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller must not provide a valid Merkle tree path for a different `(role, accountId)` pairing.
+
+
+#### _uncheckedCanProveRole [toc] [#ShieldedAccessControl-_uncheckedCanProveRole]
+#### [!toc] [#ShieldedAccessControl-_uncheckedCanProveRole]
+
+ Returns `true` if the caller proves ownership of `role` and is not revoked.
+ MAY return false for a legitimately credentialed caller if the proving environment supplies an invalid Merkle path.
+ This circuit will never return true for an unauthorized caller.
+
+ Disclosures: a Merkle tree path to a role commitment, a role commitment corresponding to a `(role, accountId)` pairing, and a nullifier for the respective role commitment.
+
+
+ This circuit does not perform an initialization check.
+ It is only meant to be used as an internal helper.
+ Using this circuit outside of the module may cause undefined behavior and break security guarantees.
+
+
+
+#### grantRole [toc] [#ShieldedAccessControl-grantRole]
+#### [!toc] [#ShieldedAccessControl-grantRole]
+
+ Grants `role` to `accountId` by inserting a role commitment unique to the
+ `(role, accountId)` pairing into the `_operatorRoles` Merkle tree. Duplicate role commitments can be issued
+ so long as they remain unrevoked. Once revoked, a role cannot be re-granted to the same `accountId`. A new
+ `accountId` must be generated to be re-authorized for a revoked `role`.
+
+ Disclosures: a Merkle tree path to a role commitment, a role commitment, a nullifier, and a role identifier.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller must prove they are an admin for `role`.
+ * Caller must not provide a valid Merkle tree path for a different `(role, accountId)` pairing.
+ * The `(role, accountId)` pairing must not be already revoked.
+
+
+#### revokeRole [toc] [#ShieldedAccessControl-revokeRole]
+#### [!toc] [#ShieldedAccessControl-revokeRole]
+
+ Permanently revokes `role` from `accountId` by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
+ `role`.
+
+
+ At this time, proofs of non-membership on values in the Merkle tree are not available,
+ so a `(role, accountId)` pairing that does not exist can still be revoked.
+
+
+ Disclosures: a Merkle tree path to a role commitment, a role commitment, a nullifier, and a role identifier.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller must prove they are an admin for `role`.
+ * Caller must not provide a valid Merkle tree path for a different `(role, accountId)` pairing.
+ * The `(role, accountId)` pairing must not be already revoked.
+
+
+#### renounceRole [toc] [#ShieldedAccessControl-renounceRole]
+#### [!toc] [#ShieldedAccessControl-renounceRole]
+
+ Revokes `role` from the calling account by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be
+ re-authorized for `role`.
+
+ Roles are often managed via [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole):
+ this circuit's purpose is to provide a mechanism for accounts to lose their privileges
+ if they are compromised (such as when a trusted device is misplaced).
+
+ Disclosures: a nullifier for the respective role commitment.
+
+
+ Outside observers may be able to use timing and pattern analysis to weaken pseudonymity
+ guarantees if `renounceRole` is used in tandem with other on-chain actions.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller must provide a valid `accountId` for the role.
+ * The `(role, accountId)` pairing must not be already revoked.
+
+
+#### _updateRole [toc] [#ShieldedAccessControl-_updateRole]
+#### [!toc] [#ShieldedAccessControl-_updateRole]
+
+ Core business logic for the grant/revoke role circuits.
+ Asserts that the `(role, accountId)` pairing has not already been revoked.
+ On success, dispatches on `updateType`: a `Grant` inserts the role commitment into `_operatorRoles`, and
+ a `Revoke` inserts the nullifier into `_roleCommitmentNullifiers`.
+
+ Disclosures: a nullifier for the respective role commitment, and a role commitment (on Grant only).
+
+
+ The nullifier is disclosed via `_roleCommitmentNullifiers.member` on every call,
+ regardless of the update type.
+ This enables observers to correlate grant and revocation transactions for the same `(role, accountId)` pairing.
+
+
+
+#### getRoleAdmin [toc] [#ShieldedAccessControl-getRoleAdmin]
+#### [!toc] [#ShieldedAccessControl-getRoleAdmin]
+
+ Returns the admin role that controls `role`. Returns `DEFAULT_ADMIN_ROLE` for
+ roles with no explicitly set admin. Since `DEFAULT_ADMIN_ROLE` is the zero byte array,
+ there is no distinction between a nonexistent `role` and one whose admin is `DEFAULT_ADMIN_ROLE`.
+ See [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole).
+
+ To change a role's admin use [_setRoleAdmin](#ShieldedAccessControl-_setRoleAdmin).
+
+ Disclosures: a role identifier.
+
+
+#### _grantRole [toc] [#ShieldedAccessControl-_grantRole]
+#### [!toc] [#ShieldedAccessControl-_grantRole]
+
+ Grants `role` to `accountId` by inserting a role commitment unique to the
+ `(role, accountId)` pairing into the `_operatorRoles` Merkle tree.
+
+ Internal circuit without access restriction.
+
+ Disclosures: a role commitment corresponding to a `(role, accountId)` pairing, and a nullifier for the respective role commitment.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to grant
+ roles without authorization. It must be wrapped with appropriate access control.
+
+
+
+ The `_operatorRoles` Merkle tree has a fixed capacity of 2^20 leaf slots.
+ Deployers should monitor slot consumption off-chain. Duplicate grants waste tree capacity
+ but are otherwise benign. A single nullifier invalidates all duplicate commitments.
+ Implementing contracts are responsible for mitigating tree exhaustion risk.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The `(role, accountId)` pairing must not be already revoked.
+
+
+#### _revokeRole [toc] [#ShieldedAccessControl-_revokeRole]
+#### [!toc] [#ShieldedAccessControl-_revokeRole]
+
+ Permanently revokes `role` from `accountId` by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
+ `role`.
+
+ Internal circuit without access restriction.
+
+ Disclosures: a nullifier for the respective role commitment.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to revoke
+ roles without authorization. It must be wrapped with appropriate access control.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The `(role, accountId)` pairing must not be already revoked.
+
+
+#### _setRoleAdmin [toc] [#ShieldedAccessControl-_setRoleAdmin]
+#### [!toc] [#ShieldedAccessControl-_setRoleAdmin]
+
+ Sets `adminId` as `role`'s admin identifier. Users with valid admin identifiers
+ may grant and revoke access to the specified `role`. Internal circuit without access restriction.
+
+ Disclosures: the role identifier and the admin identifier.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to assign
+ arbitrary admin roles without authorization. It must be wrapped with appropriate access control.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _validateRole [toc] [#ShieldedAccessControl-_validateRole]
+#### [!toc] [#ShieldedAccessControl-_validateRole]
+
+ Verifies whether `accountId` holds `role`.
+ MAY return false for a legitimately credentialed account
+ if the proving environment supplies an invalid Merkle path.
+
+ Disclosures: a Merkle tree path to a role commitment, and a nullifier for the respective role commitment.
+
+
+ This circuit does not perform an initialization check.
+ It is only meant to be used as an internal helper.
+ Using this circuit outside of the module may cause undefined behavior and break security guarantees.
+
+
+ The nullifier is disclosed via `_roleCommitmentNullifiers.member` on every call.
+ Since this circuit is invoked by `assertOnlyRole` and `canProveRole`,
+ every protected operation discloses the same nullifier,
+ allowing observers to link all actions performed under the same role instance across time.
+
+
+
+#### computeRoleCommitment [toc] [#ShieldedAccessControl-computeRoleCommitment]
+#### [!toc] [#ShieldedAccessControl-computeRoleCommitment]
+
+ Computes the role commitment from the given `role` and `accountId`.
+
+ Role Commitment Derivation: `roleCommitment = SHA256(role, accountId, instanceSalt, commitmentDomain)`
+
+
+ This circuit does not perform an initialization check. It is only meant to be used as
+ an internal helper. Using this circuit outside of the module may cause undefined behavior
+ and break security guarantees.
+
+
+
+#### computeNullifier [toc] [#ShieldedAccessControl-computeNullifier]
+#### [!toc] [#ShieldedAccessControl-computeNullifier]
+
+ Computes the role nullifier for a given `roleCommitment`.
+
+ Role Nullifier Derivation: `roleNullifier = SHA256(roleCommitment, nullifierDomain)`
+
+
+#### _computeAccountId [toc] [#ShieldedAccessControl-_computeAccountId]
+#### [!toc] [#ShieldedAccessControl-_computeAccountId]
+
+ Computes the caller's account identifier from the `wit_secretKey` witness and the `_instanceSalt`.
+
+ ID Derivation: `accountId = SHA256(secretKey, instanceSalt, accountIdDomain)`
+
+ The result is a 32-byte commitment that uniquely identifies the account.
+
+
+ This circuit does not perform an initialization check.
+ It is only meant to be used as an internal helper.
+ Using this circuit outside of the module may cause undefined behavior and break security guarantees.
+
+
+
+#### computeAccountId [toc] [#ShieldedAccessControl-computeAccountId]
+#### [!toc] [#ShieldedAccessControl-computeAccountId]
+
+ Computes an `accountId` without on-chain state, allowing a user to derive
+ their shielded identity commitment before submitting it in a grant or revoke operation.
+ This is the off-chain counterpart to the internal `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+ ID Derivation: `accountId = SHA256(secretKey, instanceSalt, accountIdDomain)`
+
+
+ The `secretKey` parameter is a sensitive secret. Mishandling it can permanently compromise the privacy guarantees of this system.
+ Never log or persist the key in plaintext. Use cryptographically secure randomness to generate keys.
+ Treat key loss as identity loss.
+ A lost key cannot be recovered.
+
+
diff --git a/content/contracts-compact/api/utils.mdx b/content/contracts-compact/api/utils.mdx
index 81a36c96..be5c264a 100644
--- a/content/contracts-compact/api/utils.mdx
+++ b/content/contracts-compact/api/utils.mdx
@@ -15,13 +15,13 @@ This package provides the API for all Utils modules.
/>
```ts
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable";
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils";
```
---
- There’s no easy way to get the constraints of circuits at this time so the constraints of the circuits listed below have been omitted.
+ There's no easy way to get the constraints of circuits at this time so the constraints of the circuits listed below have been omitted.
### Ledger [toc] [#Utils-Ledger]
@@ -95,3 +95,13 @@ None.
>
A helper function that returns the empty string: `""`.
+
+#### canonicalize [toc] [#Utils-canonicalize]
+#### [!toc] [#Utils-canonicalize]
+
+ Zeroes out the unused side of an `Either` value. Prevents crafted inputs where both `left` and `right` fields carry data from bypassing checks that only inspect the active side.
+
diff --git a/content/contracts-compact/extensibility.mdx b/content/contracts-compact/extensibility.mdx
index b8673cfe..08531c39 100644
--- a/content/contracts-compact/extensibility.mdx
+++ b/content/contracts-compact/extensibility.mdx
@@ -54,18 +54,18 @@ As Compact matures, this pattern will likely evolve as well.
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable"
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
prefix Ownable_;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Pausable"
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
prefix Pausable_;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
constructor(
_name: Maybe>,
_symbol: Maybe>,
_decimals:Uint<8>,
- _owner: Either
+ _owner: Either, ContractAddress>
) {
FungibleToken_initialize(_name, _symbol, _decimals);
Ownable_initialize(_owner);
@@ -92,20 +92,20 @@ export circuit totalSupply(): Uint<128> {
}
export circuit balanceOf(
- account: Either
+ account: Either, ContractAddress>
): Uint<128> {
return FungibleToken_balanceOf(account);
}
export circuit allowance(
- owner: Either,
- spender: Either
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
): Uint<128> {
return FungibleToken_allowance(owner, spender);
}
export circuit transfer(
- to: Either,
+ to: Either, ContractAddress>,
value: Uint<128>
): Boolean {
Pausable_assertNotPaused();
@@ -113,8 +113,8 @@ export circuit transfer(
}
export circuit transferFrom(
- from: Either,
- to: Either,
+ from: Either, ContractAddress>,
+ to: Either, ContractAddress>,
value: Uint<128>
): Boolean {
Pausable_assertNotPaused();
@@ -122,7 +122,7 @@ export circuit transferFrom(
}
export circuit approve(
- spender: Either,
+ spender: Either, ContractAddress>,
value: Uint<128>
): Boolean {
Pausable_assertNotPaused();
@@ -132,7 +132,7 @@ export circuit approve(
/** IMintable */
export circuit mint(
- account: Either,
+ account: Either, ContractAddress>,
value: Uint<128>
): [] {
Pausable_assertNotPaused();
@@ -158,12 +158,12 @@ export circuit unpause(): [] {
/** IOwnable */
-export circuit owner(): Either {
+export circuit owner(): Either, ContractAddress> {
return Ownable_owner();
}
export circuit transferOwnership(
- newOwner: Either
+ newOwner: Either, ContractAddress>
): [] {
Ownable_transferOwnership(newOwner);
}
diff --git a/content/contracts-compact/fungibleToken.mdx b/content/contracts-compact/fungible-token.mdx
similarity index 78%
rename from content/contracts-compact/fungibleToken.mdx
rename to content/contracts-compact/fungible-token.mdx
index 134a5e24..645f2777 100644
--- a/content/contracts-compact/fungibleToken.mdx
+++ b/content/contracts-compact/fungible-token.mdx
@@ -6,7 +6,7 @@ title: FungibleToken
[fungible tokens]: ../contracts/5.x/tokens.mdx#different-kinds-of-tokens
[EIP-20]: https://eips.ethereum.org/EIPS/eip-20
[Module/Contract Pattern]: ./extensibility.mdx#the_module_contract_pattern
-[_mint]: api/fungibleToken#FungibleToken-_mint
+[_mint]: api/fungible-token#FungibleToken-_mint
FungibleToken is a specification for [fungible tokens],
a type of token where all the units are exactly equal to each other.
@@ -26,7 +26,7 @@ the library uses the Compact type `Uint<128>`.
***Features and specifications NOT supported***
* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future.
-* ***Uint256 type*** - There’s ongoing research on ways to support uint256 in the future.
+* ***Uint256 type*** - There's ongoing research on ways to support uint256 in the future.
* ***Interface*** - Compact currently does not have a way to define a contract interface.
This library offers modules of contracts with free floating circuits;
nevertheless, there are no means of enforcing that all circuits are provided.
@@ -46,13 +46,13 @@ The `unsafe` circuits will eventually be deprecated after Compact supports contr
## Usage
Import the FungibleToken module into the implementing contract.
-It’s recommended to prefix the module with `FungibleToken_` to avoid circuit signature clashes.
+It's recommended to prefix the module with `FungibleToken_` to avoid circuit signature clashes.
```typescript
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
constructor(
@@ -87,20 +87,20 @@ export circuit decimals(): Uint<8> {
(...)
```
-The following example is a simple token contract with a fixed supply that’s minted to the passed recipient upon construction.
+The following example is a simple token contract with a fixed supply that's minted to the passed recipient upon construction.
```typescript
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
constructor(
name: Opaque<"string">,
symbol: Opaque<"string">,
decimals: Uint<8>,
- recipient: Either,
+ recipient: Either, ContractAddress>,
fixedSupply: Uint<128>,
) {
FungibleToken_initialize(name, symbol, decimals);
@@ -123,30 +123,30 @@ export circuit totalSupply(): Uint<128> {
return FungibleToken_totalSupply();
}
-export circuit balanceOf(account: Either): Uint<128> {
+export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
return FungibleToken_balanceOf(account);
}
export circuit allowance(
- owner: Either,
- spender: Either
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
): Uint<128> {
return FungibleToken_allowance(owner, spender);
}
-export circuit transfer(to: Either, value: Uint<128>): Boolean {
+export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean {
return FungibleToken_transfer(to, value);
}
export circuit transferFrom(
- from: Either,
- to: Either,
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
value: Uint<128>,
): Boolean {
- return FungibleToken_transferFrom(from, to, value);
+ return FungibleToken_transferFrom(fromAddress, to, value);
}
-export circuit approve(spender: Either, value: Uint<128>): Boolean {
+export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
return FungibleToken_approve(spender, value);
}
diff --git a/content/contracts-compact/index.mdx b/content/contracts-compact/index.mdx
index e13d0664..1963a214 100644
--- a/content/contracts-compact/index.mdx
+++ b/content/contracts-compact/index.mdx
@@ -38,30 +38,16 @@ mkdir my-project
cd my-project
```
-Initialize git and add OpenZeppelin Contracts for Compact as a submodule.
+Install the library.
```bash
-git init && \
-git submodule add https://github.com/OpenZeppelin/compact-contracts.git
-```
-
-`cd` into it and then install dependencies and prepare the environment.
-
-```bash
-nvm install && \
-yarn && \
-SKIP_ZK=true yarn compact
+yarn add @openzeppelin/compact-contracts
```
### Write a custom contract using library modules
In the root of `my-project`, create a custom contract using OpenZeppelin Compact modules.
-Import the modules through `compact-contracts/node_modules/@openzeppelin-compact/contracts/...`.
-Import modules through `node_modules` rather than directly to avoid state conflicts between shared dependencies.
-
-
-Installing the library will be easier once it's available as an NPM package.
-
+Import the modules through `./node_modules/@openzeppelin/compact-contracts/...`.
```typescript
// MyContract.compact
@@ -69,34 +55,93 @@ Installing the library will be easier once it's available as an NPM package.
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable"
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
prefix Ownable_;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Pausable"
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
prefix Pausable_;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
constructor(
_name: Opaque<"string">,
_symbol: Opaque<"string">,
_decimals: Uint<8>,
- _recipient: Either,
+ _recipient: Either, ContractAddress>,
_amount: Uint<128>,
- _initOwner: Either,
+ _initOwner: Either, ContractAddress>,
) {
Ownable_initialize(_initOwner);
FungibleToken_initialize(_name, _symbol, _decimals);
FungibleToken__mint(_recipient, _amount);
}
+/** IFungibleToken */
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
export circuit transfer(
- to: Either,
+ to: Either, ContractAddress>,
value: Uint<128>,
): Boolean {
Pausable_assertNotPaused();
return FungibleToken_transfer(to, value);
}
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_approve(spender, value);
+}
+
+/** IOwnable */
+
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ return Ownable_transferOwnership(newOwner);
+}
+
+export circuit renounceOwnership(): [] {
+ return Ownable_renounceOwnership();
+}
+
+/** IPausable */
+
export circuit pause(): [] {
Ownable_assertOnlyOwner();
Pausable__pause();
@@ -106,19 +151,25 @@ export circuit unpause(): [] {
Ownable_assertOnlyOwner();
Pausable__unpause();
}
-
-(...)
```
### Compile the contract
-In the project root, compile the contract using Compact's dev tools.
+In the project root, compile the contract.
```bash
% compact compile MyContract.compact artifacts/MyContract
-Compiling 3 circuits:
- circuit "pause" (k=10, rows=125)
- circuit "transfer" (k=11, rows=1180)
- circuit "unpause" (k=10, rows=121)
-Overall progress [====================] 3/3
+Compiling 11 circuits:
+ circuit "allowance" (k=11, rows=1352)
+ circuit "approve" (k=13, rows=3075)
+ circuit "balanceOf" (k=10, rows=673)
+ circuit "decimals" (k=6, rows=28)
+ circuit "name" (k=6, rows=28)
+ circuit "pause" (k=13, rows=2365)
+ circuit "symbol" (k=6, rows=28)
+ circuit "totalSupply" (k=6, rows=28)
+ circuit "transfer" (k=13, rows=3990)
+ circuit "transferFrom" (k=13, rows=4972)
+ circuit "unpause" (k=13, rows=2362)
+Overall progress [====================] 11/11
```
diff --git a/content/contracts-compact/latest-version.js b/content/contracts-compact/latest-version.js
new file mode 100644
index 00000000..52662a23
--- /dev/null
+++ b/content/contracts-compact/latest-version.js
@@ -0,0 +1 @@
+export const latestStable = "0.1.x";
diff --git a/content/contracts-compact/multitoken.mdx b/content/contracts-compact/multi-token.mdx
similarity index 79%
rename from content/contracts-compact/multitoken.mdx
rename to content/contracts-compact/multi-token.mdx
index c55bef7b..d4123f2b 100644
--- a/content/contracts-compact/multitoken.mdx
+++ b/content/contracts-compact/multi-token.mdx
@@ -6,7 +6,7 @@ title: MultiToken
[EIP-1155]: https://eips.ethereum.org/EIPS/eip-1155
[ERC165]: https://eips.ethereum.org/EIPS/eip-165
[Module/Contract Pattern]: ./extensibility.mdx#the_module_contract_pattern
-[_mint]: ./api/multitoken#MultiToken-_mint
+[_mint]: ./api/multi-token#MultiToken-_mint
MultiToken is a specification for contracts that manage multiple token types.
This module is an approximation of [EIP-1155] written in the Compact programming language for the Midnight network.
@@ -25,13 +25,13 @@ the library uses the Compact type `Uint<128>`.
**Features and specifications NOT supported**
* **Events** - Midnight does not currently support events, but this is planned on being supported in the future.
-* **Uint256 type** - There’s ongoing research on ways to support uint256 in the future.
+* **Uint256 type** - There's ongoing research on ways to support uint256 in the future.
* **Interface** - Compact currently does not have a way to define a contract interface.
This library offers modules of contracts with free floating circuits;
-nevertheless, there’s no means of enforcing that all circuits are provided.
+nevertheless, there's no means of enforcing that all circuits are provided.
* **Batch mint, burn, transfer** - Without support for dynamic arrays,
batching transfers is difficult to do without a hacky solution.
-For instance, we could change the `to` and `from` parameters to be vectors.
+For instance, we could change the `to` and `fromAddress` parameters to be vectors.
This would change the signature and would be both difficult to use and easy to misuse.
* **Querying batched balances** - This can be somewhat supported.
The issue, without dynamic arrays, is that the module circuit must use `Vector` for accounts and ids;
@@ -39,7 +39,7 @@ therefore, the implementing contract must explicitly define the number of balanc
```ts
balanceOfBatch_10(
- accounts: Vector<10, Either>,
+ accounts: Vector<10, Either, ContractAddress>>,
ids: Vector<10, Uint<128>>
): Vector<10, Uint<128>>
```
@@ -65,13 +65,13 @@ The `unsafe` circuits will eventually be deprecated after Compact supports contr
## Usage
Import the MultiToken module into the implementing contract.
-It’s recommended to prefix the module with `MultiToken_` to avoid circuit signature clashes.
+It's recommended to prefix the module with `MultiToken_` to avoid circuit signature clashes.
```typescript
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/MultiToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken"
prefix MultiToken_;
constructor(
@@ -90,11 +90,11 @@ Exposing [_mint] without some sort of access control, for example, would allow A
```typescript
export circuit uri(id: Uint<128>): Opaque<"string"> {
- return MultiToken_uri();
+ return MultiToken_uri(id);
}
-export circuit balanceOf(account: Either): Uint<128> {
- return MultiToken_balanceOf(account);
+export circuit balanceOf(account: Either, ContractAddress>, id: Uint<128>): Uint<128> {
+ return MultiToken_balanceOf(account, id);
}
(...)
@@ -108,12 +108,12 @@ The following example is a simple multi-token contract that creates both a fixed
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/MultiToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken"
prefix MultiToken_;
constructor(
_uri: Opaque<"string">,
- recipient: Either,
+ recipient: Either, ContractAddress>,
fungibleFixedSupply: Uint<128>,
) {
// `initialize` sets the URI (base) for all tokens minted from this contract
@@ -132,31 +132,31 @@ export circuit uri(id: Uint<128>): Opaque<"string"> {
return MultiToken_uri(id);
}
-export circuit balanceOf(account: Either, id: Uint<128>): Uint<128> {
+export circuit balanceOf(account: Either, ContractAddress>, id: Uint<128>): Uint<128> {
return MultiToken_balanceOf(account, id);
}
export circuit setApprovalForAll(
- operator: Either,
+ operator: Either, ContractAddress>,
approved: Boolean
): [] {
return MultiToken_setApprovalForAll(operator, approved);
}
export circuit isApprovedForAll(
- account: Either,
- operator: Either
+ account: Either, ContractAddress>,
+ operator: Either, ContractAddress>
): Boolean {
return MultiToken_isApprovedForAll(account, operator);
}
export circuit transferFrom(
- from: Either,
- to: Either,
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
id: Uint<128>,
value: Uint<128>,
): [] {
- return MultiToken_transferFrom(from, to, id, value);
+ return MultiToken_transferFrom(fromAddress, to, id, value);
}
```
diff --git a/content/contracts-compact/nonFungibleToken.mdx b/content/contracts-compact/non-fungible-token.mdx
similarity index 76%
rename from content/contracts-compact/nonFungibleToken.mdx
rename to content/contracts-compact/non-fungible-token.mdx
index 50c2f031..a70985ac 100644
--- a/content/contracts-compact/nonFungibleToken.mdx
+++ b/content/contracts-compact/non-fungible-token.mdx
@@ -7,7 +7,7 @@ title: NonFungibleToken
[EIP-721]: https://eips.ethereum.org/EIPS/eip-721
[ERC-165]: https://eips.ethereum.org/EIPS/eip-165
[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
-[_mint]: ./api/nonFungibleToken#NonFungibleToken-_mint
+[_mint]: ./api/non-fungible-token#NonFungibleToken-_mint
NonFungibleToken is a specification for [non-fungible tokens],
a type of token where all the units are unique and distinct from each other.
@@ -26,18 +26,18 @@ the library uses the Compact type `Uint<128>`.
* ***No _baseURI() support*** - Native strings and string operations are not supported within the Compact language,
so concatenating a base URI + token ID is not possible like in other NFT implementations.
Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs.
-It’s up to the implementation to decide on how to handle this.
+It's up to the implementation to decide on how to handle this.
***Features and specifications NOT supported***
* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future.
-* ***Uint256 type*** - There’s ongoing research on ways to support uint256 in the future.
+* ***Uint256 type*** - There's ongoing research on ways to support uint256 in the future.
* ***Interface*** - Compact currently does not have a way to define a contract interface.
This library offers modules of contracts with free floating circuits;
nevertheless, there are no means of enforcing that all circuits are provided.
* ***ERC-165 Standard*** - Since Compact doesn't provide a way to define a contract interface,
-it’s not possible to implement an [ERC-165] like interface standard at this time.
-* ***Safe Transfers*** - It’s not possible to implement safe transfers without an [ERC-165]-like
+it's not possible to implement an [ERC-165] like interface standard at this time.
+* ***Safe Transfers*** - It's not possible to implement safe transfers without an [ERC-165]-like
interface standard at this time.
## Contract-to-contract calls
@@ -55,13 +55,13 @@ The `unsafe` circuits will eventually be deprecated after Compact supports contr
## Usage
Import the NonFungibleToken module into the implementing contract.
-It’s recommended to prefix the module with `NonFungibleToken_` to avoid circuit signature clashes.
+It's recommended to prefix the module with `NonFungibleToken_` to avoid circuit signature clashes.
```typescript
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken"
prefix NonFungibleToken_;
constructor(name: Opaque<"string">, symbol: Opaque<"string">) {
@@ -96,13 +96,13 @@ The following example is a simple non-fungible token contract that mints an NFT
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken"
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken"
prefix NonFungibleToken_;
constructor(
name: Opaque<"string">,
symbol: Opaque<"string">,
- recipient: Either,
+ recipient: Either, ContractAddress>,
tokenURI: Opaque<"string">,
) {
const tokenId = 1 as Uint<128>;
@@ -111,11 +111,11 @@ constructor(
NonFungibleToken__setTokenURI(tokenId, tokenURI);
}
-export circuit balanceOf(owner: Either): Uint<128> {
+export circuit balanceOf(owner: Either, ContractAddress>): Uint<128> {
return NonFungibleToken_balanceOf(owner);
}
-export circuit ownerOf(tokenId: Uint<128>): Either {
+export circuit ownerOf(tokenId: Uint<128>): Either, ContractAddress> {
return NonFungibleToken_ownerOf(tokenId);
}
@@ -131,34 +131,34 @@ export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> {
return NonFungibleToken_tokenURI(tokenId);
}
-export circuit approve(to: Either, tokenId: Uint<128>): [] {
+export circuit approve(to: Either, ContractAddress>, tokenId: Uint<128>): [] {
NonFungibleToken_approve(to, tokenId);
}
-export circuit getApproved(tokenId: Uint<128>): Either {
+export circuit getApproved(tokenId: Uint<128>): Either, ContractAddress> {
return NonFungibleToken_getApproved(tokenId);
}
export circuit setApprovalForAll(
- operator: Either,
+ operator: Either, ContractAddress>,
approved: Boolean
): [] {
NonFungibleToken_setApprovalForAll(operator, approved);
}
export circuit isApprovedForAll(
- owner: Either,
- operator: Either
+ owner: Either, ContractAddress>,
+ operator: Either, ContractAddress>
): Boolean {
return NonFungibleToken_isApprovedForAll(owner, operator);
}
export circuit transferFrom(
- from: Either,
- to: Either,
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
tokenId: Uint<128>
): [] {
- NonFungibleToken_transferFrom(from, to, tokenId);
+ NonFungibleToken_transferFrom(fromAddress, to, tokenId);
}
```
diff --git a/content/contracts-compact/ownable.mdx b/content/contracts-compact/ownable.mdx
index d441f96d..5deb387f 100644
--- a/content/contracts-compact/ownable.mdx
+++ b/content/contracts-compact/ownable.mdx
@@ -8,63 +8,99 @@ title: Ownable
[_transferOwnership]: api/ownable#Ownable-_transferOwnership
[_unsafeTransferOwnership]: api/ownable#Ownable-_unsafeTransferOwnership
[_unsafeUncheckedTransferOwnership]: api/ownable#Ownable-_unsafeUncheckedTransferOwnership
+[computeAccountId]: api/ownable#Ownable-computeAccountId
+[assertOnlyOwner]: api/ownable#Ownable-assertOnlyOwner
+[Ownable API]: api/ownable#Ownable
+
+[ZOwnablePK_initialize]: api/ownable#ZOwnablePK-initialize
+[ZOwnablePK_transferOwnership]: api/ownable#ZOwnablePK-transferOwnership
+[ZOwnablePK_assertOnlyOwner]: api/ownable#ZOwnablePK-assertOnlyOwner
[_computeOwnerId]: api/ownable#ZOwnablePK-_computeOwnerId
-[assertOnlyOwner]: api/ownable#ZOwnablePK-assertOnlyOwner
[ed25519]: https://ed25519.cr.yp.to/
[rfc6979]: https://datatracker.ietf.org/doc/html/rfc6979
+[AccessControl]: ./access-control.mdx
## Access Control Defined
Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts.
The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things.
It is therefore critical to understand how you implement it, lest someone else steals your whole system.
-OpenZeppelin Contracts for Compact provides a variety of access control modules to suit your application and privacy needs.
+This library provides a variety of access control modules to suit your application and privacy needs.
## Ownership and `Ownable`
The most common and basic form of access control is the concept of ownership:
-there’s an account that is the owner of a contract and can do administrative tasks on it.
+there's an account that is the owner of a contract and can do administrative tasks on it.
This approach is perfectly reasonable for contracts that have a single administrative user.
-OpenZeppelin Contracts for Compact provides an Ownable module for implementing ownership in your contracts.
+This library provides an Ownable module for implementing ownership in your contracts.
The initial owner must be set by using the [initialize] circuit during construction.
This can later be changed with [transferOwnership].
-### Ownership transfers
+
+ For more granular, role-based access control, see [AccessControl].
+
+
+### Identity Model
+
+Ownable uses a witness-derived identity scheme. The owner proves knowledge of a secret key
+by injecting it via the `wit_OwnableSK` witness. The module computes an account identifier
+as `persistentHash(secretKey)`, which is a commitment that hides the key while providing a stable,
+pseudonymous on-chain identity.
+
+To derive an account identifier off-chain (e.g. for the `initialOwner` parameter during construction),
+use [computeAccountId]:
+
+```typescript
+const accountId = Ownable_computeAccountId(secretKey);
+```
+
+The `secretKey` is a 32-byte cryptographically secure random value that the owner keeps private.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of ownership.
+ Key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+### Ownership Transfers
-Ownership can only be transferred to `ZswapCoinPublicKeys` through the main transfer circuits ([transferOwnership] and [_transferOwnership]).
+Ownership can only be transferred to `Bytes<32>` account identifiers through the main transfer circuits
+([transferOwnership] and [_transferOwnership]).
In other words, ownership transfers to contract addresses are disallowed through these circuits.
-This is because Compact currently does not support contract-to-contract calls which means if a contract is granted ownership,
+This is because Compact currently does not support contract-to-contract calls, which means if a contract is granted ownership,
the owner contract cannot directly call the protected circuit.
-### Experimental features
+### Experimental Features
-This module offers experimental circuits that allow ownership to be granted to contract addresses ([_unsafeTransferOwnership] and [_unsafeUncheckedTransferOwnership]).
+This module offers experimental circuits that allow ownership to be granted to contract addresses
+([_unsafeTransferOwnership] and [_unsafeUncheckedTransferOwnership]).
Note that the circuit names are very explicit ("unsafe") with these experimental circuits.
Until contract-to-contract calls are supported,
there is no direct way for a contract to call circuits of other contracts or transfer ownership back to a user.
-The unsafe circuits are planned to become deprecated once contract-to-contract calls become available.
+ The unsafe circuits are planned to become deprecated once contract-to-contract calls become available.
### Usage
Import the Ownable module into the implementing contract.
-It’s recommended to prefix the module with `Ownable_` to avoid circuit signature clashes.
+It's recommended to prefix the module with `Ownable_` to avoid circuit signature clashes.
+
+The `initialOwner` should be an account identifier derived off-chain using `computeAccountId(secretKey)`:
```ts
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable"
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
prefix Ownable_;
constructor(
- initialOwner: Either
+ initialOwner: Either, ContractAddress>
) {
Ownable_initialize(initialOwner);
}
@@ -82,14 +118,15 @@ export circuit mySensitiveCircuit(): [] {
```
Contracts may expose [transferOwnership] to allow the owner to transfer ownership.
+The `newOwner` should be an account identifier derived by the new owner using `computeAccountId(secretKey)`:
```ts
-export circuit transferOwnership(newOwner: Either): [] {
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
Ownable_transferOwnership(newOwner);
}
```
-Here’s a complete contract showcasing how to integrate the Ownable module and protect sensitive circuits.
+Here's a complete contract showcasing how to integrate the Ownable module and protect sensitive circuits.
```ts
// SimpleOwnable.compact
@@ -97,28 +134,30 @@ Here’s a complete contract showcasing how to integrate the Ownable module and
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable"
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
prefix Ownable_;
/**
* Set `initialOwner` as the owner of the contract.
+ * `initialOwner` should be derived off-chain using computeAccountId(secretKey).
*/
-constructor(initialOwner: Either) {
+constructor(initialOwner: Either, ContractAddress>) {
Ownable_initialize(initialOwner);
}
/**
- * The current owner of the contact.
+ * The current owner of the contract.
*/
-export circuit owner(): Either {
+export circuit owner(): Either, ContractAddress> {
return Ownable_owner();
}
/**
* Transfers ownership of the contract.
* Can only be called by the current owner.
+ * `newOwner` should be derived off-chain by the new owner using computeAccountId(secretKey).
*/
-export circuit transferOwnership(newOwner: Either): [] {
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
Ownable_transferOwnership(newOwner);
}
@@ -151,20 +190,18 @@ export circuit mySensitiveCircuit(): [] {
## Shielded Ownership and `ZOwnablePK`
Privacy-preserving access control is a fundamental building block for confidential smart contracts on Midnight.
-While traditional ownership patterns expose the owner’s identity on-chain,
+While the `Ownable` module stores the owner's account identifier on-chain,
many applications require administrative control without revealing who holds that authority.
### Privacy-First Ownership
-The most common approach to access control in traditional smart contracts is ownership:
-there’s an account that is the owner of a contract and can perform administrative tasks.
-However, this approach reveals the owner’s identity to all observers, creating privacy and security risks.
-In privacy-sensitive applications—such as confidential voting systems, private treasuries,
-or anonymous governance—revealing the administrator’s identity may compromise the entire system’s confidentiality.
-This library provides the `ZOwnablePK` module that implements shielded ownership—administrative control without identity disclosure.
-The owner’s public key is never revealed on-chain;
+The `ZOwnablePK` module implements shielded ownership meaning administrative control without identity disclosure.
+The owner's public key is never revealed on-chain;
instead, the contract stores only a cryptographic commitment that proves ownership without exposing the underlying identity.
+This is useful in scenarios such as private treasuries, anonymous governance,
+or any application where revealing the administrator's identity may compromise the system's confidentiality.
+
### Commitment Scheme
The `ZOwnablePK` module employs a two-layer cryptographic commitment scheme designed to provide privacy,
@@ -178,10 +215,10 @@ The foundation of the system is the owner identifier, computed as:
id = SHA256(pk, nonce)
```
-Where `pk` is the owner’s public key and `nonce` is a secret value that may be either randomly generated for maximum privacy
+Where `pk` is the owner's public key and `nonce` is a secret value that may be either randomly generated for maximum privacy
or deterministically derived for recoverability.
This identifier serves as a privacy-preserving alternative to exposing the raw public key,
-ensuring the owner’s identity remains confidential.
+ensuring the owner's identity remains confidential.
#### Owner Commitment Computation
@@ -196,10 +233,10 @@ This multi-element hash provides several security properties:
* `id`: The privacy-preserving owner identifier described above.
* `instanceSalt`: A unique per-deployment salt that prevents commitment collisions across different contract instances,
even when the same owner and nonce are used.
-* `counter`: Incremented with each ownership transfer to ensure unlinkability—each transfer produces a completely different commitment
-even with the same underlying owner.
-* `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions with other commitment schemes
-and enables safe protocol extensions.
+* `counter`: Incremented with each ownership transfer to ensure unlinkability.
+Each transfer produces a completely different commitment even with the same underlying owner.
+* `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions
+with other commitment schemes and enables safe protocol extensions.
#### Security Properties
@@ -223,8 +260,7 @@ const randomNonce = crypto.getRandomValues(new Uint8Array(32));
const ownerId = ZOwnablePK._computeOwnerId(publicKey, randomNonce);
```
-This approach is easy to generate and ensures maximum unlinkability—even with sophisticated analysis,
-observers cannot correlate ownership across different contracts or time periods.
+This approach is easy to generate and ensures maximum unlinkability.
However, it requires secure backup of both the private key and the nonce.
**Loss of either component results in permanent, irrecoverable loss of ownership.**
@@ -232,9 +268,9 @@ However, it requires secure backup of both the private key and the nonce.
Deriving the nonce deterministically enables recovery through derivation schemes. Some examples:
-* `H(passphrase + context)` - recoverable from passphrase only, but passphrase becomes critical single point of failure.
-* `H(publicKey + userPassphrase + context)` - requires both public key and passphrase.
-* `H(signature + context) where signature = sign(context)` - leverages wallet without exposing private key.
+* `H(passphrase + context)` — recoverable from passphrase only, but passphrase becomes critical single point of failure.
+* `H(publicKey + userPassphrase + context)` — requires both public key and passphrase.
+* `H(signature + context) where signature = sign(context)` — leverages wallet without exposing private key.
When using signature-based nonce derivation,
@@ -253,22 +289,6 @@ Deriving the nonce deterministically enables recovery through derivation schemes
are generally recommended for operational security.
-Deriving the nonce deterministically from an [Air-Gapped Public Key](#air-gapped-public-key-agpk)
-and user passphrase provides a balance of security and recoverability:
-
-```typescript
-// Example: Scrypt-based derivation
-import { scryptSync } from 'node:crypto';
-
-const deterministicNonce = scryptSync(
- userPassphrase
- publicKey + ":ZOwnablePK:nonce:v1",
- 32,
- { N: 16384, r: 8, p: 1 } // Standard scrypt parameters
-);
-const recoverableOwnerId = ZOwnablePK._computeOwnerId(publicKey, deterministicNonce);
-```
-
**Security Considerations**
The `ZOwnablePK` module remains agnostic to nonce generation methods,
@@ -278,53 +298,15 @@ Key considerations include:
* **Backup requirements**: Random nonces require additional secure storage.
* **Recovery scenarios**: Deterministic nonces enable recovery.
* **Cross-contract correlation**: Reusing nonce strategies may reduce privacy across deployments.
-* **Rotation costs**: Changing nonces requires ownership transfer transactions with associated DUST costs.
+* **Rotation costs**: Changing nonces requires ownership transfer transactions with associated costs.
Users should carefully evaluate their threat model, operational requirements, and privacy needs when selecting a nonce generation strategy,
as this choice cannot be easily changed without transferring ownership.
-### Air-Gapped Public Key (AGPK)
-
-For maximum privacy guarantees,
-users should employ an Air-Gapped Public Key (AGPK) exclusively for contract ownership and administrative circuits.
-An AGPK is a public key that maintains complete isolation from all other on-chain activities,
-similar to how air-gapped systems are isolated from networks to prevent data leakage.
-
-#### The Privacy Enhancement
-
-While `ZOwnablePK` provides cryptographic privacy through its commitment scheme,
-operational security practices like using an AGPK provide an additional layer of protection against correlation attacks.
-Even with the strongest cryptographic commitments,
-reusing a public key across different on-chain activities can potentially compromise privacy through transaction pattern analysis.
-
-#### AGPK Principles
-
-An Air-Gapped Public Key must adhere to strict isolation principles:
-
-* **Never used before**: The private key material (including any seed, parent key, or entropy source from which this key is derived)
-has never generated any public key that appears in any on-chain transaction, across any blockchain network.
-The key material must be cryptographically pure.
-* **Never used elsewhere**: From the moment of AGPK generation until its destruction,
-the private key material is used exclusively for this contract’s administrative functions (i.e. [assertOnlyOwner]).
-No other public keys may ever be derived from or generated with the same key material.
-* **Never used again**: Users commit to destroying all copies of the private key material upon ownership renunciation or transfer.
-This relies entirely on user discipline and cannot be externally verified or enforced.
-
-**Best Practices Recommendation**
-
-While neither required nor enforced by the `ZOwnablePK` module,
-an Air-Gapped Public Key provides strong operational privacy hygiene for shielded contract administration.
-Users should evaluate their threat model and privacy requirements when deciding whether to implement AGPK practices.
-
-
- The effectiveness of an AGPK depends entirely on abiding by the AGPK principles.
- A single transaction using the key outside the administrative context compromises all privacy benefits.
-
-
### Usage
Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits.
-It’s recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes.
+It's recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes.
```typescript
// MyZOwnablePKContract.compact
@@ -332,7 +314,7 @@ It’s recommended to prefix the module with `ZOwnablePK_` to avoid circuit sign
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK"
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK"
prefix ZOwnablePK_;
constructor(
@@ -356,7 +338,7 @@ export circuit renounceOwnership(): [] {
```
Similar to the [Ownable](#usage) module,
-circuits can be protected so that only the contract owner may them by adding `assertOnlyOwner` as the first line in the circuit body like this:
+circuits can be protected so that only the contract owner may call them by adding `assertOnlyOwner` as the first line in the circuit body:
```typescript
export circuit mySensitiveCircuit(): [] {
@@ -367,12 +349,12 @@ export circuit mySensitiveCircuit(): [] {
```
This covers the basics for creating a contract, but before deploying the contract,
-the owner’s id must be derived for the commitment scheme because it’s required to deploy the contract.
+the owner's id must be derived for the commitment scheme because it's required to deploy the contract.
-First, the owner needs to generate a secret nonce that’s stored in the owner’s private state.
+First, the owner needs to generate a secret nonce that's stored in the owner's private state.
See [Nonce Generation Strategies](#nonce-generation-strategies).
-Once the owner has the secret nonce generated, they can insert their public key and nonce into the following:
+Once the owner has the secret nonce generated, they can derive their owner ID:
```typescript
import {
@@ -398,7 +380,5 @@ const generateInstanceSalt = (): Uint8Array => {
```
- Another way to get the user ID is to expose [_computeOwnerId] in the contract and call this circuit off chain through a contract simulator.
- Be on the lookout for future tooling that makes this process easier.
-
+ Another way to get the user ID is to expose [_computeOwnerId] in the contract and call this circuit off-chain through a contract simulator.
diff --git a/content/contracts-compact/security.mdx b/content/contracts-compact/security.mdx
index af857de7..e8419e1b 100644
--- a/content/contracts-compact/security.mdx
+++ b/content/contracts-compact/security.mdx
@@ -24,7 +24,8 @@ Many modules also use the initializable pattern which ensures that implementing
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable';
+import "./node_modules/@openzeppelin/compact-contracts/security/Initializable"
+ prefix Initializable_;
export ledger _fieldAfterDeployment: Field;
@@ -71,12 +72,12 @@ For example (using the [Ownable] module for access control):
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable'
- prefix Initializable_;
-import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable'
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
prefix Ownable_;
-constructor(initOwner: Either) {
+constructor(initOwner: Either, ContractAddress>) {
Ownable_initialize(initOwner);
}
diff --git a/content/contracts-compact/shielded-access-control.mdx b/content/contracts-compact/shielded-access-control.mdx
new file mode 100644
index 00000000..e3a3d34c
--- /dev/null
+++ b/content/contracts-compact/shielded-access-control.mdx
@@ -0,0 +1,193 @@
+---
+title: ShieldedAccessControl
+---
+
+{/* links */}
+[Role-Based Access Control (RBAC)]: https://en.wikipedia.org/wiki/Role-based_access_control
+[AccessControl]: ./access-control.mdx
+[ShieldedAccessControl API]: api/shielded-access-control
+
+[assertOnlyRole]: api/shielded-access-control#ShieldedAccessControl-assertOnlyRole
+[canProveRole]: api/shielded-access-control#ShieldedAccessControl-canProveRole
+[grantRole]: api/shielded-access-control#ShieldedAccessControl-grantRole
+[_grantRole]: api/shielded-access-control#ShieldedAccessControl-_grantRole
+[revokeRole]: api/shielded-access-control#ShieldedAccessControl-revokeRole
+[_revokeRole]: api/shielded-access-control#ShieldedAccessControl-_revokeRole
+[renounceRole]: api/shielded-access-control#ShieldedAccessControl-renounceRole
+[_setRoleAdmin]: api/shielded-access-control#ShieldedAccessControl-_setRoleAdmin
+[getRoleAdmin]: api/shielded-access-control#ShieldedAccessControl-getRoleAdmin
+[computeAccountId]: api/shielded-access-control#ShieldedAccessControl-computeAccountId
+[DEFAULT_ADMIN_ROLE]: api/shielded-access-control#ShieldedAccessControl-DEFAULT_ADMIN_ROLE
+
+This module provides a **privacy-preserving** role-based access control mechanism.
+Unlike [AccessControl], which stores role assignments in a public map,
+ShieldedAccessControl stores roles as Merkle tree commitments which hides _who_ holds a role
+while still allowing on-chain verification of authorization.
+
+If your contract does not require privacy for role holders, the simpler [AccessControl] module is recommended.
+
+## Why Shielded Access Control?
+
+In many applications, revealing which accounts hold administrative or privileged roles
+creates a security and privacy risk. ShieldedAccessControl addresses this by ensuring
+that role holders are never identified on-chain. Instead, callers prove ownership of a role
+using zero-knowledge proofs against Merkle tree commitments.
+
+This is useful in scenarios such as:
+
+- **Private governance**: voters or committee members prove eligibility without revealing identity.
+- **Confidential operations**: admins can manage a system without their addresses being publicly linked to the contract.
+- **Pseudonymous authorization**: role holders maintain a stable pseudonymous identity for the lifetime of their role, but that identity cannot be linked to a real-world identity without key compromise.
+
+## How It Works
+
+ShieldedAccessControl is built on three cryptographic primitives:
+
+**Role commitments** are Merkle tree leaves that bind a role to an account without revealing either.
+When a role is granted, a commitment is inserted into a Merkle tree:
+
+```
+roleCommitment = SHA256(role, accountId, instanceSalt, commitmentDomain)
+```
+
+**Account identifiers** are privacy-preserving identity commitments derived from a secret key
+and a per-deployment instance salt. A single secret key can be used across multiple roles within the same contract:
+
+```
+accountId = SHA256(secretKey, instanceSalt, accountIdDomain)
+```
+
+**Role nullifiers** are one-time burn tokens. When a role is revoked, its nullifier is inserted into a set,
+permanently invalidating the corresponding commitment:
+
+```
+roleNullifier = SHA256(roleCommitment, nullifierDomain)
+```
+
+The `instanceSalt` is a per-deployment random value that prevents commitment collisions across contracts
+and ensures that the same secret key produces different identities in different deployments.
+
+## Using `ShieldedAccessControl`
+
+Usage follows the same pattern as [AccessControl] which is to define role identifiers,
+grant them during construction, and enforce them with [assertOnlyRole].
+The key difference is that role assignments are stored as Merkle tree commitments rather than in a public map,
+and revocation uses a nullifier scheme rather than simply flipping a boolean.
+Account identifiers also incorporate a per-instance salt, preventing cross-contract correlation of the same secret key.
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl"
+ prefix SAC_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+
+constructor(
+ instanceSalt: Bytes<32>,
+ defaultAdmin: SAC_AccountIdentifier,
+ minter: SAC_AccountIdentifier
+) {
+ SAC_initialize(instanceSalt);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ // Grant the default admin role and the minter role
+ SAC__grantRole(SAC_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+ SAC__grantRole(MINTER_ROLE as SAC_RoleIdentifier, minter);
+}
+
+export circuit mint(/* ... */): [] {
+ SAC_assertOnlyRole(MINTER_ROLE as SAC_RoleIdentifier);
+ // ... minting logic
+}
+```
+
+
+ The `instanceSalt` must be generated using a cryptographically secure random number generator
+ (e.g. `crypto.getRandomValues()`) prior to deployment. Weak or predictable salts weaken privacy guarantees.
+
+
+### Deriving Account Identifiers
+
+Before an account can be granted a role, the grantor needs the grantee's `AccountIdentifier`.
+This is derived off-chain using [computeAccountId]:
+
+```ts
+const accountId = SAC_computeAccountId(secretKey, instanceSalt);
+```
+
+The `secretKey` is a 32-byte cryptographically secure random value that the user keeps private.
+The `instanceSalt` is the same value used during contract initialization.
+
+
+ The `secretKey` must never be logged, persisted in plaintext, or exposed in browser devtools.
+ Loss of the key means loss of all roles held under that identity. Key exposure allows impersonation
+ and retroactive deanonymization.
+
+
+### Granting and Revoking Roles
+
+Role management works similarly to [AccessControl]. Every role has an associated admin role.
+Only accounts that can prove they hold the admin role may call [grantRole] and [revokeRole].
+By default, all roles are administered by [DEFAULT_ADMIN_ROLE].
+Custom admin relationships can be set using [_setRoleAdmin].
+
+There is one important difference from unshielded AccessControl:
+**revocation is permanent for a given identity**. When a role is revoked,
+its nullifier is burned, and the same `(role, accountId)` pairing can never be granted again.
+To be re-authorized, the user must generate a new `secretKey` to produce a new `accountId`.
+
+Other roles held under the old secret key remain valid. The user will need to retain both keys
+until all roles under the old key are no longer needed.
+
+### Renouncing Roles
+
+[renounceRole] allows a role holder to voluntarily give up their own role.
+This is useful if a trusted device is compromised and the holder wants to immediately invalidate their credentials.
+The caller must confirm their `accountId` to prevent accidental renunciation.
+
+### Proving Role Ownership
+
+Two circuits are available for verifying role ownership:
+
+- [assertOnlyRole]: reverts if the caller cannot prove they hold the role. Use this to gate access to circuits.
+- [canProveRole]: returns a boolean without reverting. Useful for conditional logic where lacking a role is not an error.
+
+Both circuits disclose a nullifier, which means every call made under the same role instance
+can be linked by an outside observer. This provides **pseudonymity, not anonymity**.
+
+## Privacy Considerations
+
+ShieldedAccessControl provides strong privacy guarantees, but it's important to understand what is and isn't hidden:
+
+**What observers CAN see:**
+- When roles are granted and revoked
+- All role identifiers and which are admin identifiers (disclosed via [getRoleAdmin], [_setRoleAdmin], [grantRole], and [revokeRole])
+- How many admins exist
+- All role nullifiers (disclosed via `Set` operations in every grant, revoke, and role-proof transaction)
+- The same nullifier across calls, allowing them to link all actions performed under the same role instance over time (pseudonymity, not anonymity)
+- Correlation between a grant and its revocation (the same nullifier appears in both transactions)
+- Which role is being granted or revoked when the admin-checked circuits ([grantRole], [revokeRole]) are used, since those disclose the role identifier in the same transaction
+- The cumulative number of role grants across all roles; per-role counts are also inferable when the admin-checked circuits are used
+
+**What observers CANNOT see:**
+- The identity of any role holder, so long as secret keys are kept private and generated using cryptographically secure random values
+- The value of role commitments stored in the Merkle tree (MerkleTree inserts hash the value internally; only the internal leaf hash appears as a public input, unlike Set operations where values are directly visible)
+- Users can be retroactively deanonymized if their secret key is exposed
+
+
+ The internal circuits ([_grantRole], [_revokeRole]) do not disclose the role identifier, offering stronger privacy
+ when wrapped with custom authorization logic. The trade-off is that the implementing contract takes full
+ responsibility for access control.
+
+
+## Security Considerations
+
+- The `_operatorRoles` Merkle tree has a **fixed capacity of 2^20 leaf slots** and slots cannot be reclaimed after revocation. Monitor slot consumption off-chain and plan capacity accordingly.
+- A single secret key compromise exposes **all roles** held by that user within the contract instance. Users requiring compartmentalization between roles should use separate keys per role.
+- Admins can revoke `(role, accountId)` pairings that were never granted (non-membership proofs are not available for the Merkle tree). Admin trust is a fundamental assumption. Off-chain validation should confirm a pairing was actually granted before submitting a revocation.
+- The SHA256 hashing function is used throughout. A migration to a ZK-friendly hashing function is planned when an implementation becomes available.
+
+
+ For the full API reference, see the [ShieldedAccessControl API].
+
diff --git a/content/contracts-compact/utils.mdx b/content/contracts-compact/utils.mdx
index 66b41195..1437ed3e 100644
--- a/content/contracts-compact/utils.mdx
+++ b/content/contracts-compact/utils.mdx
@@ -10,7 +10,7 @@ The Utils module provides miscellaneous circuits and common utilities for Compac
pragma language_version >= {{compact_language_version}};
import CompactStandardLibrary;
-import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/utils/Utils'
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
prefix Utils_;
export circuit performActionWhenEqual(
diff --git a/content/contracts-compact/utils/constants.js b/content/contracts-compact/utils/constants.js
index a204daa8..27c5d08b 100644
--- a/content/contracts-compact/utils/constants.js
+++ b/content/contracts-compact/utils/constants.js
@@ -1,2 +1,2 @@
-export const COMPACT_COMPILER_VERSION = "0.24.0";
-export const COMPACT_LANGUAGE_VERSION = "0.16.0";
+export const COMPACT_COMPILER_VERSION = "0.29.0";
+export const COMPACT_LANGUAGE_VERSION = "0.21.0";
diff --git a/content/contracts-compact/zkCircuits101.mdx b/content/contracts-compact/zk-circuits-101.mdx
similarity index 100%
rename from content/contracts-compact/zkCircuits101.mdx
rename to content/contracts-compact/zk-circuits-101.mdx
diff --git a/public/llms.txt b/public/llms.txt
index 748e141d..c4afd851 100644
--- a/public/llms.txt
+++ b/public/llms.txt
@@ -323,18 +323,18 @@ Each ecosystem section lists the smart-contract libraries and language-specific
- [Overview](https://docs.openzeppelin.com/contracts-compact)
- [Learn — Extensibility](https://docs.openzeppelin.com/contracts-compact/extensibility)
-- [Learn — ZK Circuits 101](https://docs.openzeppelin.com/contracts-compact/zkCircuits101)
+- [Learn — ZK Circuits 101](https://docs.openzeppelin.com/contracts-compact/zk-circuits-101)
- [Modules — Access — Ownable](https://docs.openzeppelin.com/contracts-compact/ownable)
-- [Modules — Access — AccessControl](https://docs.openzeppelin.com/contracts-compact/accessControl)
+- [Modules — Access — AccessControl](https://docs.openzeppelin.com/contracts-compact/access-control)
- [Modules — Security](https://docs.openzeppelin.com/contracts-compact/security)
-- [Modules — Tokens — FungibleToken](https://docs.openzeppelin.com/contracts-compact/fungibleToken)
-- [Modules — Tokens — NonFungibleToken](https://docs.openzeppelin.com/contracts-compact/nonFungibleToken)
-- [Modules — Tokens — MultiToken](https://docs.openzeppelin.com/contracts-compact/multitoken)
+- [Modules — Tokens — FungibleToken](https://docs.openzeppelin.com/contracts-compact/fungible-token)
+- [Modules — Tokens — NonFungibleToken](https://docs.openzeppelin.com/contracts-compact/non-fungible-token)
+- [Modules — Tokens — MultiToken](https://docs.openzeppelin.com/contracts-compact/multi-token)
- [Modules — Utils](https://docs.openzeppelin.com/contracts-compact/utils)
-- [API Reference — AccessControl API](https://docs.openzeppelin.com/contracts-compact/api/accessControl)
-- [API Reference — FungibleToken API](https://docs.openzeppelin.com/contracts-compact/api/fungibleToken)
-- [API Reference — MultiToken API](https://docs.openzeppelin.com/contracts-compact/api/multitoken)
-- [API Reference — NonFungibleToken API](https://docs.openzeppelin.com/contracts-compact/api/nonFungibleToken)
+- [API Reference — AccessControl API](https://docs.openzeppelin.com/contracts-compact/api/access-control)
+- [API Reference — FungibleToken API](https://docs.openzeppelin.com/contracts-compact/api/fungible-token)
+- [API Reference — MultiToken API](https://docs.openzeppelin.com/contracts-compact/api/multi-token)
+- [API Reference — NonFungibleToken API](https://docs.openzeppelin.com/contracts-compact/api/non-fungible-token)
- [API Reference — Ownable API](https://docs.openzeppelin.com/contracts-compact/api/ownable)
- [API Reference — Security API](https://docs.openzeppelin.com/contracts-compact/api/security)
- [API Reference — Utils API](https://docs.openzeppelin.com/contracts-compact/api/utils)
diff --git a/src/navigation/ecosystems.json b/src/navigation/ecosystems.json
index 0752df2a..23f1aaca 100644
--- a/src/navigation/ecosystems.json
+++ b/src/navigation/ecosystems.json
@@ -71,7 +71,7 @@
{
"type": "page",
"name": "ZK Circuits 101",
- "url": "/contracts-compact/zkCircuits101"
+ "url": "/contracts-compact/zk-circuits-101"
},
{
"type": "page",
diff --git a/src/navigation/midnight.json b/src/navigation/midnight.json
index d1411012..793f569e 100644
--- a/src/navigation/midnight.json
+++ b/src/navigation/midnight.json
@@ -21,7 +21,7 @@
{
"type": "page",
"name": "ZK Circuits 101",
- "url": "/contracts-compact/zkCircuits101"
+ "url": "/contracts-compact/zk-circuits-101"
}
]
},
@@ -41,7 +41,12 @@
{
"type": "page",
"name": "AccessControl",
- "url": "/contracts-compact/accessControl"
+ "url": "/contracts-compact/access-control"
+ },
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl",
+ "url": "/contracts-compact/shielded-access-control"
}
]
},
@@ -57,17 +62,17 @@
{
"type": "page",
"name": "FungibleToken",
- "url": "/contracts-compact/fungibleToken"
+ "url": "/contracts-compact/fungible-token"
},
{
"type": "page",
"name": "NonFungibleToken",
- "url": "/contracts-compact/nonFungibleToken"
+ "url": "/contracts-compact/non-fungible-token"
},
{
"type": "page",
"name": "MultiToken",
- "url": "/contracts-compact/multitoken"
+ "url": "/contracts-compact/multi-token"
}
]
},
@@ -85,22 +90,22 @@
{
"type": "page",
"name": "AccessControl API",
- "url": "/contracts-compact/api/accessControl"
+ "url": "/contracts-compact/api/access-control"
},
{
"type": "page",
"name": "FungibleToken API",
- "url": "/contracts-compact/api/fungibleToken"
+ "url": "/contracts-compact/api/fungible-token"
},
{
"type": "page",
"name": "MultiToken API",
- "url": "/contracts-compact/api/multitoken"
+ "url": "/contracts-compact/api/multi-token"
},
{
"type": "page",
"name": "NonFungibleToken API",
- "url": "/contracts-compact/api/nonFungibleToken"
+ "url": "/contracts-compact/api/non-fungible-token"
},
{
"type": "page",
@@ -112,6 +117,11 @@
"name": "Security API",
"url": "/contracts-compact/api/security"
},
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl API",
+ "url": "/contracts-compact/api/shielded-access-control"
+ },
{
"type": "page",
"name": "Utils API",