Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(accounts): Store signing public keys in immutable private notes in account contracts #1080

Merged
merged 10 commits into from
Jul 19, 2023
Merged
4 changes: 3 additions & 1 deletion yarn-project/acir-simulator/src/acvm/acvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const ONE_ACVM_FIELD: ACVMField = `0x${'00'.repeat(Fr.SIZE_IN_BYTES - 1)}
type ORACLE_NAMES =
| 'packArguments'
| 'getSecretKey'
| 'getNote'
spalladino marked this conversation as resolved.
Show resolved Hide resolved
| 'getNotes'
| 'getRandomField'
| 'notifyCreatedNote'
Expand All @@ -40,7 +41,8 @@ type ORACLE_NAMES =
| 'emitEncryptedLog'
| 'emitUnencryptedLog'
| 'getPublicKey'
| 'debugLog';
| 'debugLog'
| 'debugLogWithPrefix';

/**
* A type that does not require all keys to be present.
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/acir-simulator/src/client/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ACVMField } from '../acvm/index.js';
* @param msg - array of ACVMFields where each represents a single ascii character
* @returns string representation of the message
*/
function acvmFieldMessageToString(msg: ACVMField[]): string {
export function acvmFieldMessageToString(msg: ACVMField[]): string {
let msgStr = '';
for (const msgChar of msg) {
const asciiCode = Number(msgChar);
Expand Down Expand Up @@ -81,10 +81,10 @@ function processFieldOrArray(fieldOrArray: string[]) {
}

// Check if all the elements start with 63 zero bytes
// --> if yes, we have an array of bytes and we print as decimal values
// --> if yes, we have an array of bytes and we print as hex
if (onlyBytes(fieldOrArray)) {
const decimalArray = fieldOrArray.map(element => parseInt(element, 16));
return '[' + decimalArray.join(', ') + ']';
return '0x' + Buffer.from(decimalArray).toString('hex');
}

return '[' + fieldOrArray.join(', ') + ']';
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/acir-simulator/src/client/private_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from '../acvm/index.js';
import { ExecutionResult, NewNoteData, NewNullifierData } from '../index.js';
import { ClientTxExecutionContext } from './client_execution_context.js';
import { oracleDebugCallToFormattedStr } from './debug.js';
import { acvmFieldMessageToString, oracleDebugCallToFormattedStr } from './debug.js';

/**
* The private function execution class.
Expand Down Expand Up @@ -127,6 +127,10 @@ export class PrivateFunctionExecution {
this.log(oracleDebugCallToFormattedStr(args));
return Promise.resolve(ZERO_ACVM_FIELD);
},
debugLogWithPrefix: (arg0, ...args) => {
this.log(`${acvmFieldMessageToString(arg0)}: ${oracleDebugCallToFormattedStr(args)}`);
return Promise.resolve(ZERO_ACVM_FIELD);
},
enqueuePublicFunctionCall: async ([acvmContractAddress], [acvmFunctionSelector], [acvmArgsHash]) => {
const enqueuedRequest = await this.enqueuePublicFunctionCall(
frToAztecAddress(fromACVMField(acvmContractAddress)),
Expand Down
12 changes: 9 additions & 3 deletions yarn-project/aztec-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { randomBytes } from '@aztec/foundation/crypto';
import { JsonStringify } from '@aztec/foundation/json-rpc';
import { createLogger } from '@aztec/foundation/log';
import { createDebugLogger } from '@aztec/foundation/log';
import { SchnorrAccountContractAbi } from '@aztec/noir-contracts/examples';
import { SchnorrSingleKeyAccountContractAbi } from '@aztec/noir-contracts/examples';
import { ContractData, L2BlockL2Logs, TxHash } from '@aztec/types';

import { Command } from 'commander';
Expand Down Expand Up @@ -92,7 +92,13 @@ async function main() {
.action(async options => {
const client = createAztecRpcClient(options.rpcUrl);
const privateKey = options.privateKey && Buffer.from(options.privateKey.replace(/^0x/i, ''), 'hex');
const wallet = await createAccounts(client, SchnorrAccountContractAbi, privateKey, accountCreationSalt, 1);
const wallet = await createAccounts(
client,
SchnorrSingleKeyAccountContractAbi,
privateKey,
accountCreationSalt,
1,
);
const accounts = await wallet.getAccounts();
const pubKeys = await Promise.all(accounts.map(acc => wallet.getAccountPublicKey(acc)));
log(`\nCreated account(s).`);
Expand Down Expand Up @@ -274,7 +280,7 @@ async function main() {
const client = createAztecRpcClient(options.rpcUrl);
const wallet = await getAccountWallet(
client,
SchnorrAccountContractAbi,
SchnorrSingleKeyAccountContractAbi,
Buffer.from(options.privateKey, 'hex'),
accountCreationSalt,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@aztec/aztec.js';
import { createDebugLogger } from '@aztec/foundation/log';
import { UniswapPortalAbi, UniswapPortalBytecode } from '@aztec/l1-artifacts';
import { SchnorrAccountContractAbi } from '@aztec/noir-contracts/examples';
import { SchnorrSingleKeyAccountContractAbi } from '@aztec/noir-contracts/examples';
import { NonNativeTokenContract, UniswapContract } from '@aztec/noir-contracts/types';
import { AztecRPC, TxStatus } from '@aztec/types';

Expand Down Expand Up @@ -169,7 +169,7 @@ const transferWethOnL2 = async (
async function main() {
logger('Running L1/L2 messaging test on HTTP interface.');

wallet = await createAccounts(aztecRpcClient, SchnorrAccountContractAbi, privateKey!, Fr.random(), 2);
wallet = await createAccounts(aztecRpcClient, SchnorrSingleKeyAccountContractAbi, privateKey!, Fr.random(), 2);
const accounts = await wallet.getAccounts();
const [owner, receiver] = accounts;

Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Contract, Wallet, createAccounts, createAztecRpcClient } from '@aztec/aztec.js';
import { AztecAddress, Fr } from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';
import { SchnorrAccountContractAbi } from '@aztec/noir-contracts/examples';
import { SchnorrSingleKeyAccountContractAbi } from '@aztec/noir-contracts/examples';
import { ZkTokenContract } from '@aztec/noir-contracts/types';

const logger = createDebugLogger('aztec:http-rpc-client');
Expand Down Expand Up @@ -49,7 +49,7 @@ async function getBalance(contract: Contract, ownerAddress: AztecAddress) {
async function main() {
logger('Running ZK contract test on HTTP interface.');

wallet = await createAccounts(aztecRpcClient, SchnorrAccountContractAbi, privateKey, Fr.random(), 2);
wallet = await createAccounts(aztecRpcClient, SchnorrSingleKeyAccountContractAbi, privateKey, Fr.random(), 2);
const accounts = await aztecRpcClient.getAccounts();
const [ownerAddress, address2] = accounts;
logger(`Created ${accounts.length} accounts`);
Expand Down
47 changes: 47 additions & 0 deletions yarn-project/aztec.js/src/abis/ecdsa_account_contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,53 @@
}
],
"returnTypes": []
},
{
"name": "stev",
"functionType": "unconstrained",
"parameters": [
{
"name": "contract_address",
"type": {
"kind": "field"
},
"visibility": "private"
},
{
"name": "nonce",
"type": {
"kind": "field"
},
"visibility": "private"
},
{
"name": "storage_slot",
"type": {
"kind": "field"
},
"visibility": "private"
},
{
"name": "preimage",
"type": {
"kind": "array",
"length": 5,
"type": {
"kind": "field"
}
},
"visibility": "private"
}
],
"returnTypes": [
{
"kind": "array",
"length": 4,
"type": {
"kind": "field"
}
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"name": "SchnorrSingleKeyAccount",
"functions": [
{
"name": "constructor",
"functionType": "secret",
"parameters": [],
"returnTypes": []
},
{
"name": "entrypoint",
"functionType": "secret",
"parameters": [
{
"name": "payload",
"type": {
"kind": "struct",
"fields": [
{
"name": "flattened_args_hashes",
"type": {
"kind": "array",
"length": 2,
"type": {
"kind": "field"
}
}
},
{
"name": "flattened_selectors",
"type": {
"kind": "array",
"length": 2,
"type": {
"kind": "field"
}
}
},
{
"name": "flattened_targets",
"type": {
"kind": "array",
"length": 2,
"type": {
"kind": "field"
}
}
},
{
"name": "nonce",
"type": {
"kind": "field"
}
}
]
},
"visibility": "public"
},
{
"name": "owner",
"type": {
"kind": "array",
"length": 64,
"type": {
"kind": "integer",
"sign": "unsigned",
"width": 8
}
},
"visibility": "public"
},
{
"name": "signature",
"type": {
"kind": "array",
"length": 64,
"type": {
"kind": "integer",
"sign": "unsigned",
"width": 8
}
},
"visibility": "public"
},
{
"name": "partial_address",
"type": {
"kind": "field"
},
"visibility": "public"
}
],
"returnTypes": []
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ExecutionRequest, PackedArguments, TxExecutionRequest } from '@aztec/ty

import partition from 'lodash.partition';

import SchnorrAccountContractAbi from '../abis/schnorr_account_contract.json' assert { type: 'json' };
import SchnorrSingleKeyAccountContractAbi from '../abis/schnorr_account_contract.json' assert { type: 'json' };
import { generatePublicKey } from '../index.js';
import { buildPayload, hashPayload } from './entrypoint_payload.js';
import { AccountImplementation } from './index.js';
Expand Down Expand Up @@ -57,10 +57,10 @@ export class SingleKeyAccountContract implements AccountImplementation {
}

private getEntrypointAbi() {
// We use the SchnorrAccountContract because it implements the interface we need, but ideally
// We use the SchnorrSingleKeyAccountContract because it implements the interface we need, but ideally
// we should have an interface that defines the entrypoint for SingleKeyAccountContracts and
// load the abi from it.
const abi = (SchnorrAccountContractAbi as any as ContractAbi).functions.find(f => f.name === 'entrypoint');
const abi = (SchnorrSingleKeyAccountContractAbi as any as ContractAbi).functions.find(f => f.name === 'entrypoint');
if (!abi) throw new Error(`Entrypoint abi for account contract not found`);
return abi;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AztecAddress, CircuitsWasm, FunctionData, TxContext } from '@aztec/circuits.js';
import { Signer } from '@aztec/circuits.js/barretenberg';
import { ContractAbi, encodeArguments, generateFunctionSelector } from '@aztec/foundation/abi';
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { ExecutionRequest, PackedArguments, TxExecutionRequest } from '@aztec/types';

import partition from 'lodash.partition';
Expand All @@ -14,7 +15,11 @@ import { AccountImplementation } from './index.js';
* every new request in order to validate the payload signature.
*/
export class StoredKeyAccountContract implements AccountImplementation {
constructor(private address: AztecAddress, private privateKey: Buffer, private signer: Signer) {}
private log: DebugLogger;

constructor(private address: AztecAddress, private privateKey: Buffer, private signer: Signer) {
this.log = createDebugLogger('aztec:client:accounts:stored_key');
}

getAddress(): AztecAddress {
return this.address;
Expand All @@ -31,8 +36,9 @@ export class StoredKeyAccountContract implements AccountImplementation {
const wasm = await CircuitsWasm.get();
const { payload, packedArguments: callsPackedArguments } = await buildPayload(privateCalls, publicCalls);
const hash = hashPayload(payload);

const signature = this.signer.constructSignature(hash, this.privateKey).toBuffer();
this.log(`Signed challenge ${hash.toString('hex')} as ${signature.toString('hex')}`);

const args = [payload, signature];
const abi = this.getEntrypointAbi();
const selector = generateFunctionSelector(abi.name, abi.parameters);
Expand Down
1 change: 1 addition & 0 deletions yarn-project/circuits.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"dependencies": {
"@aztec/foundation": "workspace:^",
"@msgpack/msgpack": "^3.0.0-beta2",
"@noble/curves": "^1.0.0",
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.times": "^4.3.7",
"cross-fetch": "^3.1.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('ecdsa', () => {
ecdsa = new Ecdsa(wasm);
});

it('should verify signature', () => {
it.skip('should verify signature', () => {
// prettier-ignore
const privateKey = Buffer.from([
0x0b, 0x9b, 0x3a, 0xde, 0xe6, 0xb3, 0xd8, 0x1b, 0x28, 0xa0, 0x88, 0x6b, 0x2a, 0x84, 0x15, 0xc7,
Expand All @@ -25,7 +25,7 @@ describe('ecdsa', () => {
expect(verified).toBe(true);
});

it('should recover public key from signature', () => {
it.skip('should recover public key from signature', () => {
// prettier-ignore
const privateKey = Buffer.from([
0x0b, 0x9b, 0x3a, 0xde, 0xe6, 0xb3, 0xd8, 0x1b, 0x28, 0xa0, 0x88, 0x6b, 0x2a, 0x84, 0x15, 0xc7,
Expand Down
18 changes: 15 additions & 3 deletions yarn-project/circuits.js/src/barretenberg/crypto/ecdsa/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
import { numToUInt32BE } from '@aztec/foundation/serialize';
import { IWasmModule } from '@aztec/foundation/wasm';

import { secp256k1 } from '@noble/curves/secp256k1';

import { CircuitsWasm } from '../../../index.js';
import { Signer } from '../index.js';
import { EcdsaSignature } from './signature.js';
Expand Down Expand Up @@ -43,10 +47,18 @@ export class Ecdsa implements Signer {
this.wasm.writeMemory(mem, msg);
this.wasm.call('ecdsa__construct_signature', mem, msg.length, 0, 32, 64, 96);

// TODO(#913): Understand why this doesn't work
// const sig = new EcdsaSignature(
// Buffer.from(this.wasm.getMemorySlice(32, 64)),
// Buffer.from(this.wasm.getMemorySlice(64, 96)),
// Buffer.from(this.wasm.getMemorySlice(96, 97)),
// );

const signature = secp256k1.sign(msg, privateKey);
return new EcdsaSignature(
Buffer.from(this.wasm.getMemorySlice(32, 64)),
Buffer.from(this.wasm.getMemorySlice(64, 96)),
Buffer.from(this.wasm.getMemorySlice(96, 97)),
toBufferBE(signature.r, 32),
toBufferBE(signature.s, 32),
numToUInt32BE(signature.recovery!).subarray(3, 4),
);
}

Expand Down
1 change: 1 addition & 0 deletions yarn-project/end-to-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@aztec/p2p": "workspace:^",
"@aztec/types": "workspace:^",
"@jest/globals": "^29.5.0",
"@noble/curves": "^1.0.0",
"@types/jest": "^29.5.0",
"@types/levelup": "^5.1.2",
"@types/lodash.every": "^4.6.7",
Expand Down
Loading