Skip to content

Commit

Permalink
Add canonical address registry contract + fixed circuit (#437)
Browse files Browse the repository at this point in the history
* have circuit take msg directly to avoid replays, leave digest calculation for luke

* fix typo in comment

* changeset

* fix bitmath

* fix posec comment

* add draft contracts

* add script to gen test case to core

* add eip712 test case for canon addr registry entry

* fix nits

* add canonical address registry contract

* remove abicoder v2, tob rec

* add unit test with mock verifier for canon addr registry

* make all license comments in sol files mit or apache 2

* update deploy script and e2e tests to include canon addr registry

* rename deposit request hash file in core to deposit request

* prog: e2e test submits canon addr set tx success

* finish success test case for registry

* add rest of e2e tests for registry

* save snap progress before fixing op digest calc to include canon addr

* snap builds with method to sign registry entry

* expose fe sdk method for proving and registering addr

* add compressedCanonAddr to digest, contract unit tests pass, need to fix fe-sdk and snap

* fix: fe sdk passes canon addr as part of canon addr entry

* fix e2e tests and add canon addr to entry

* cleanup

* changesets

* save progress, bad digest and msg.sender e2e test calls should fail but are not

* fix: require verifyProof in addr registry, e2e tests use chai as promised

* post rebase yarn i

* use mask instead of mod 2^252

* rebase

---------

Co-authored-by: Luke Tchang <[email protected]>
  • Loading branch information
Sladuca and luketchang authored Sep 7, 2023
1 parent d38c29e commit 77c4063
Show file tree
Hide file tree
Showing 130 changed files with 1,183 additions and 273 deletions.
7 changes: 7 additions & 0 deletions .changeset/lemon-seals-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nocturne-xyz/frontend-sdk": minor
"@nocturne-xyz/core": minor
"@nocturne-xyz/snap": minor
---

Add functionality to snap/fe-sdk that supports signing a canon addr registry entry and returning necessary inputs for sig check proof gen
5 changes: 5 additions & 0 deletions .changeset/nine-colts-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nocturne-xyz/contracts": minor
---

Add CanonicalAddressRegistry contract which integrates canon addr sig check verifier, add unit tests as well for EIP712 hashing and registry state changes
5 changes: 5 additions & 0 deletions .changeset/seven-pears-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nocturne-xyz/e2e-tests": minor
---

Add e2e tests for canon addr registry
6 changes: 6 additions & 0 deletions .changeset/shaggy-mirrors-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nocturne-xyz/config": minor
"@nocturne-xyz/deploy": minor
---

Add CanonicalAddressRegistry and sig check verifier to deploy and config packages
7 changes: 7 additions & 0 deletions .changeset/strong-bottles-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nocturne-xyz/local-prover": minor
"@nocturne-xyz/circuits": minor
"@nocturne-xyz/core": minor
---

`CanonAddrSigCheck` circuit takes msg directly as PI instead of computing it from nonce
2 changes: 1 addition & 1 deletion apps/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Nocturne Snap",
"proposedName": "Nocturne Snap",
"source": {
"shasum": "DPVJY8DPB32NH+M+ygkEpJerk/4VIB/ayFmK4dLxLU8=",
"shasum": "t7UUeyOUMmPyKVAsYiLManWR4FXmCHuu2InSoQB0uos=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
48 changes: 36 additions & 12 deletions apps/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
parseObjectValues,
signOperation,
assertAllRpcMethodsHandled,
CANON_ADDR_SIG_CHECK_PREFIX,
computeCanonAddrRegistryEntryDigest,
} from "@nocturne-xyz/core";
import * as JSON from "bigint-json-serialization";
import { ethers } from "ethers";
import { makeSignOperationContent } from "./utils/display";
import {
makeSignCanonAddrRegistryEntryContent,
makeSignOperationContent,
} from "./utils/display";
import { loadNocturneConfigBuiltin } from "@nocturne-xyz/config";
import { poseidonBN } from "@nocturne-xyz/crypto-utils";

// To build locally, invoke `yarn build:local` from snap directory
// Sepolia
Expand Down Expand Up @@ -81,15 +83,37 @@ async function handleRpcRequest({
vk: viewer.vk,
vkNonce: viewer.vkNonce,
};
case "nocturne_getCanonAddrSigCheckProofInputs":
const msg = poseidonBN([CANON_ADDR_SIG_CHECK_PREFIX, params.nonce]);
const sig = signer.sign(msg);
const vkNonce = signer.vkNonce;
const spendPubkey = signer.spendPk;
case "nocturne_signCanonAddrRegistryEntry":
const { entry, chainId, registryAddress } = request.params;

const { heading: registryHeading, text: registryText } =
makeSignCanonAddrRegistryEntryContent(entry, chainId, registryAddress);
const registryConfirmRes = await snap.request({
method: "snap_dialog",
params: {
type: "confirmation",
content: panel([heading(registryHeading), text(registryText)]),
},
});

if (!registryConfirmRes) {
throw new Error("snap request rejected by user");
}

const registryDigest = computeCanonAddrRegistryEntryDigest(
entry,
chainId,
registryAddress
);

const canonAddr = signer.canonicalAddress();
const registrySig = signer.sign(registryDigest);
const spendPubkey = signer.spendPk;
const vkNonce = signer.vkNonce;
return {
canonAddr,
sig,
digest: registryDigest,
sig: registrySig,
spendPubkey,
vkNonce,
};
Expand All @@ -105,21 +129,21 @@ async function handleRpcRequest({
return [heading(item.heading), text(item.text)];
});
// Confirm spend sig auth
const res = await snap.request({
const opConfirmRes = await snap.request({
method: "snap_dialog",
params: {
type: "confirmation",
content: panel(contentItems),
},
});

if (!res) {
if (!opConfirmRes) {
throw new Error("snap request rejected by user");
}

console.log("signing operation:", op);
try {
const signedOp = await signOperation(signer, op);
const signedOp = signOperation(signer, op);
console.log(
"PreProofOperationInputsAndProofInputs: ",
JSON.stringify(signedOp)
Expand Down
23 changes: 22 additions & 1 deletion apps/snap/src/utils/display.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Erc20Config } from "@nocturne-xyz/config";
import { OperationMetadata } from "@nocturne-xyz/core";
import {
Address,
CanonAddrRegistryEntry,
OperationMetadata,
} from "@nocturne-xyz/core";
import { formatUnits } from "ethers/lib/utils";

const lookupTickerByAddress = (
Expand All @@ -15,6 +19,23 @@ const lookupTickerByAddress = (
return addressToTicker.get(address.toLowerCase());
};

export const makeSignCanonAddrRegistryEntryContent = (
entry: CanonAddrRegistryEntry,
chainId: bigint,
registryAddress: Address
): {
heading: string;
text: string;
} => {
const heading = "Confirm signature to register canonical address";
const text = `Ethereum Address: ${entry.ethAddress}. Nocturne Canonical Address Nonce: ${entry.perCanonAddrNonce}. Chain id: ${chainId}. Registry address: ${registryAddress}`;

return {
heading,
text,
};
};

export const makeSignOperationContent = (
opMetadata: OperationMetadata,
erc20s: Map<string, Erc20Config>
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
20 changes: 10 additions & 10 deletions circuit-artifacts/canonAddrSigCheck/canonAddrSigCheck_cpp/vkey.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@
],
"vk_delta_2": [
[
"773020532580825197989265700683499343769904901528688421551138914726744641180",
"8293403394473099785006220829731294292309334277329635922514577348444877204455"
"4761012584205755692308002816946713932223363139636637093345018063225829232378",
"21285461408339149836819464155186191181043011339951578416163229016346697502857"
],
[
"8044298410607931596843420349541257429151935437174909763540021311308466536301",
"17214381066153793863275327145188716040063142813916029058922963729943312663323"
"3708429478878159669294543428116416333829890959022760504722154175354904797285",
"9010708584991867243174853970985319898237997106661774008190236299361556403304"
],
[
"1",
Expand Down Expand Up @@ -81,18 +81,18 @@
],
"IC": [
[
"4832813700246065920361343021419859188951987917247921272000554979262446849501",
"16450982492709346235822465585897720176511819063596563345522452720051071864672",
"9434419984474851429999818556499498720905949788669563531761832810248946719929",
"12786292846794097404189108015078340477344834475506107175458891298573398835448",
"1"
],
[
"20319267411373749280521656549635790467688687986098419353391917503577446154512",
"17574942179666147033145072864414125540440799097149046608123758211273599911170",
"1351205185386233948088849642139304433481076955124622098499493284515349624853",
"10015969721710150341012001783889115514198280567466133853343765962332911106146",
"1"
],
[
"17449537628454795474707997846067025590393693011235612812267573590657605090044",
"16721453092532359171509851342766105079684512390183817843689473273663565221671",
"14199811613614573570404011326440427786868292110859980021297382954977743404378",
"2453275421666044051857979484118280237638805026224254999178926792238356037468",
"1"
]
]
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion fixtures/canonAddrSigCheckProof.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"proof":{"pi_a":["4707566568477662226701048704629002296898023495689302474085085543854193803155","3644575967738185505850689153930594197798302000091737453074352963009621583047","1"],"pi_b":[["13069141789356215556738799393349390032904395438451693951284966024185000982701","16413079359372682889686551964213736164909267325081402914478067411060909601738"],["10662132772867170602735388807233470982369982452803154206295065507017214400127","4210609573482297197474333202124807106129386536279608053368214238189911285716"],["1","0"]],"pi_c":["4336617563514886771836809062099125132568962461354180077456839313813411275943","6202904228419388614700373200548865108297023268079905783649845080424671268049","1"],"protocol":"groth16","curve":"bn128"},"publicSignals":["6590372629931178525044320278222256515723582191188933114746797360106155515468n","1453n"]}
{"proof":{"pi_a":["5630047084404145852196878404202071164693044570330721321676781341333588762104","5255535139083995876723395176671055509390864255299011749228285185193695228901","1"],"pi_b":[["12283154930299705562303463526757988689290201186127646279800710714166407338636","6516582753066308242934026658946013128564007414906215796376877261005233776079"],["1034684672340757360878561019818456289937999191141133820206372131499300689111","12008549011081544495151508087453846999773609320011469369232264723680826797336"],["1","0"]],"pi_c":["1365930388966472203706781819223319674052243487788680138590293792437643151816","21289716135235906338137180885035493028168989099470691987747086359134740017437","1"],"protocol":"groth16","curve":"bn128"},"publicSignals":["6590372629931178525044320278222256515723582191188933114746797360106155515468n","1453n"]}
28 changes: 10 additions & 18 deletions packages/circuits/circuits/canonAddrSigCheck.circom
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,45 @@ pragma circom 2.1.0;
include "lib.circom";
include "include/babyjub.circom";

//@ensures(1) `compressedCanonAddr` and the sign bit from `nonceAndSignBit` are a valid canonical address
//@ensures(2) the prover can produce a valid signature of the message
// `CANONICAL_ADDRESS_REGISTRY_PREFIX | nonce` using the
// spending key corresponding the canonical address given in public inputs
//@ensures(1) `compressedCanonAddr` and the sign bit from `msgAndSignBit` are a valid canonical address
//@ensures(2) the prover can produce a valid signature of bottom 252-bits of `msgAndSignBit` with sk corresponding to the canon addr encoded in PIs
template CanonAddrSigCheck() {
// *** PUBLIC INPUTS ***
signal input compressedCanonAddrY;
signal input nonceAndSignBit;
signal input msgAndSignBit;

// *** WITNESS ***
// signature used to prove knowledge of spending key
signal input sig[2];
signal input spendPubkey[2];
signal input vkNonce;

signal nonceAndSignBitBits[65] <== Num2Bits(65)(nonceAndSignBit);
signal signBit <== nonceAndSignBitBits[64];
signal nonce <== nonceAndSignBit - (1 << 64) * signBit;
signal msgAndSignBitBits[253] <== Num2Bits(253)(msgAndSignBit);
signal signBit <== msgAndSignBitBits[252];
signal msg <== msgAndSignBit - (1 << 252) * signBit;

BabyCheck()(spendPubkey[0], spendPubkey[1]);
IsOrderL()(spendPubkey[0], spendPubkey[1]);

// keccak256("nocturne-canonical-address-registry") % l (baby jubjub scalar field order)
var CANONICAL_ADDRESS_REGISTRY_PREFIX = 116601516046334945492116181810016234440204750152070409904129749171886331002;
signal msg <== Poseidon(2)([CANONICAL_ADDRESS_REGISTRY_PREFIX, nonce]);

//@lemma(1) prover can generate valid sig for `CANONICAL_ADDRESS_REGISTRY_PREFIX | nonce` against spendPubkey
//@lemma(1) prover can generate valid sig against spendPubkey
//@argument `SigVerify.requires(1)` is guaranteed by checks above. lemma follows from `SigVerify.ensures(1)`
SigVerify()(spendPubkey, msg, sig);

//@satisfies(2)
//@argument `VKDerivation.requires(1)` is guranteed by checks above.
// `VKDerivation.ensures(3, 1)` => vkBits is the LE repr of the correct VK derived from spendPubkey and vkNonce
// => `CanonAddr.requires(1)` is satisfied. Then, (2) follows from `CanonAddr.ensures(2)` and `@lemma(1)`
// => `CanonAddr.requires(1)` is satisfied. Then, (2) follows from `CanonAddr.ensures(2)`, `@lemma(1)`, and compression checks below
//@satisfies(1) follows from (2) and `CanonAddr.ensures(1)`
component vkDerivation = VKDerivation();
vkDerivation.spendPubkey <== spendPubkey;
vkDerivation.vkNonce <== vkNonce;
signal vkBits[251] <== vkDerivation.vkBits;
signal canonAddr[2] <== CanonAddr()(vkBits);

//@satisfies(1)
//@argument `CanonAddr` above ensures that canonAddr is a valid canonical address.
// the checks below ensure that it matches the one given in PIs. Therefore (1) is satisfied.
component compressor = CompressPoint();
compressor.in <== canonAddr;
compressedCanonAddrY === compressor.y;
signBit === compressor.sign;
}

component main { public [compressedCanonAddrY, nonceAndSignBit] } = CanonAddrSigCheck();
component main { public [compressedCanonAddrY, msgAndSignBit] } = CanonAddrSigCheck();
4 changes: 4 additions & 0 deletions packages/config/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export class NocturneConfig {
});
}

canonicalAddressRegistryAddress(): Address {
return this.contracts.canonicalAddressRegistryProxy.proxy;
}

tellerAddress(): Address {
return this.contracts.tellerProxy.proxy;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/config/src/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ export interface NocturneContractDeployment {
depositManagerOwner: Address;
};
proxyAdmin: Address;
canonicalAddressRegistryProxy: ProxyAddresses<any>;
depositManagerProxy: ProxyAddresses<any>;
tellerProxy: ProxyAddresses<any>;
handlerProxy: ProxyAddresses<any>;
joinSplitVerifierAddress: Address;
subtreeUpdateVerifierAddress: Address;
canonAddrSigCheckVerifierAddress: Address;
depositSources: Address[];
screeners: Address[];
}
3 changes: 1 addition & 2 deletions packages/contracts/contracts/BalanceManager.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.17;
pragma abicoder v2;

// Internal
import {ITeller} from "./interfaces/ITeller.sol";
Expand Down
73 changes: 73 additions & 0 deletions packages/contracts/contracts/CanonAddrRegistryEntryEIP712.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.17;

// External
import {ECDSAUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
// Internal
import {Utils} from "./libs/Utils.sol";
import "./libs/Types.sol";

/// @title CanonAddrRegistryEntryEIP712
/// @author Nocturne Labs
/// @notice Base contract for CanonicalAddressRegistry containing EIP712 signing logic for canon
/// addr registry entries
contract CanonAddrRegistryEntryEIP712 is EIP712Upgradeable {
uint256 constant BOTTOM_252_MASK = (1 << 252) - 1;

bytes32 public constant CANON_ADDR_REGISTRY_ENTRY_TYPEHASH =
keccak256(
bytes(
"CanonAddrRegistryEntry(address ethAddress,uint256 compressedCanonAddr,uint256 perCanonAddrNonce)"
)
);

uint256 constant MODULUS_252 = 2 ** 252;

/// @notice Internal initializer
/// @param contractName Name of the contract
/// @param contractVersion Version of the contract
function __CanonAddrRegistryEntryEIP712_init(
string memory contractName,
string memory contractVersion
) internal onlyInitializing {
__EIP712_init(contractName, contractVersion);
}

/// @notice Computes EIP712 digest of canon addr registry entry
/// @param entry Canon addr registry entry
/// @dev The returned uint256 is masked to zero out the top 4 MSBs. The top 3 MSBs
/// are 0 because the circuit verifier must take elems <= 253 bits. The 4th MSB is 0 to
/// leave space for the sign bit of the compressed canon addr.
function _computeDigest(
CanonAddrRegistryEntry memory entry
) public view returns (uint256) {
bytes32 domainSeparator = _domainSeparatorV4();
bytes32 structHash = _hashCanonAddrRegistryEntry(entry);

bytes32 digest = ECDSAUpgradeable.toTypedDataHash(
domainSeparator,
structHash
);

// Only take bottom 252 bits to fit compressed addr sign bit in 253rd PI bit
return uint256(digest) & BOTTOM_252_MASK;
}

/// @notice Hashes canon addr registry entry
/// @param entry Canon addr registry entry
function _hashCanonAddrRegistryEntry(
CanonAddrRegistryEntry memory entry
) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
CANON_ADDR_REGISTRY_ENTRY_TYPEHASH,
entry.ethAddress,
entry.compressedCanonAddr,
entry.perCanonAddrNonce
)
);
}
}
Loading

0 comments on commit 77c4063

Please sign in to comment.