-
Notifications
You must be signed in to change notification settings - Fork 316
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: simulate, aliases, ECDSA R account contract + touchid wallet (#…
…7725) # TLDR; A bunch of improvements for `aztec-wallet`. Try this! ```bash cd ~/repos/aztec-packages/noir-projects/noir-contracts aztec-wallet create-account -a main aztec-wallet deploy token_contract@Token --args accounts:main Test TST 18 -ac main aztec-wallet send mint_public -ca contracts:last --args accounts:main 42 -ac main aztec-wallet simulate balance_of_public -ca contracts:last --args accounts:main -ac main ``` ## Aliases (explicit and automatic) Options on the wallet were getting out of hand. The ability to alias accounts partially alleviated the issue, but even moderately complex requests required +5 parameters that had to be copied all over the place. To solve this problem, the wallet now has a more general concept of aliases, plus autogeneration of some of them. For example: ```bash # set an explicit alias on an account aztec-wallet create-account -a main # Used the `accounts:main` alias in an argument and `main` alias in the `-ac` option. # The wallet is smart enough to deduce you're probably referring to an account in the latter. # Finally, set an explicit alias on the deployed contract, `my_contract` aztec-wallet deploy ../target/contract_artifact-MyContract.json --args accounts:main -ac main -a my_contract ``` But wait! There's more. You could have done: ```bash # `last` is an automatic alias on the latest object you created. In this case, the latest account! aztec-wallet deploy ../target/contract_artifact-MyContract.json --args accounts:last -ac last -a my_contract ``` So now, it is possible to do: ```bash # Used the `contracts:last` alias to refer to the address of the last contract that was deployed. # Used the `artifacts:last` alias to refer to the path of the artifact of the last contract that was deployed. azctec-wallet send my_function -c artifacts:last -ac contracts:last --args accounts:main -ac main # But we can do better! # Notice how the `-ca` argument (contract artifact) is omitted, # as the wallet is smart enough to assume that you're probably referring to the path of the artifact of the contract that was deployed at `contracts:last` azctec-wallet send my_function -ca contracts:last --args accounts:main -ac main # in this case, this is equivalent to: azctec-wallet send my_function -ca contracts:my_contract --args accounts:main -ac main ``` ## Nargo project detection For the `deploy` subcommand argument and in every instance you would provide a `-c` (contract artifact) option you could just execute the wallet from the contract folder (where `Nargo.toml` is located) ```bash cd to/the/folder/that/contains/your/contract # The wallet can detect you're in a nargo workspace and pick the artifact path automatically aztec-wallet deploy --args accounts:main -ac last ``` If it's not an isolated contract but a workspace (much like `noir-projects/noir-contracts`) ```bash cd to/the/nargo/workspace/that/contains/your/contract # It is possible to just provide the package and omit the contract name, but any ambiguity will result on the command failing aztec-wallet deploy contract_package@ContractName --args accounts:main -ac last ``` ## ECDSA secp256r1 and secp256k1 account contracts + SSH_AGENT and TouchID support It is now possible to create accounts with other than `SchnorrAccountContract` as backend (even though it's still the default). Particularly interesting is the ECDSA secp256r1 + SSH agent account, which will use the default SSH_SOCK_AUTH env variable to establish connection with a running SSH agent to perform authentication. On a Mac this can be combined with [secretive](https://github.com/maxgoedjen/secretive) for a secure enclave hardware wallet! ```bash # After installing secretive, this env variable is provided in a wizard. It can also be checked using its "help" menu export SSH_AUTH_SOCK=/Users/<myUser>/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh aztec-wallet create-account -a main -t ecdsasecp256r1ssh ``` You will be prompted to use one of the stored SSH keys inside the agent (identified by their public key), and once selected, your account will use TouchID to sign any requests the wallet might create. ## Simulate Wallet is now able to simulate functions (including unconstrained!) ```bash aztec-wallet simulate balance_of_private -c contracts:token --args accounts:main -ac main ```
- Loading branch information
Showing
37 changed files
with
1,174 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 2 additions & 1 deletion
3
...ntracts/ecdsa_account_contract/Nargo.toml → ...racts/ecdsa_k_account_contract/Nargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
[package] | ||
name = "ecdsa_account_contract" | ||
name = "ecdsa_k_account_contract" | ||
authors = [""] | ||
compiler_version = ">=0.25.0" | ||
type = "contract" | ||
|
||
[dependencies] | ||
aztec = { path = "../../../aztec-nr/aztec" } | ||
authwit = { path = "../../../aztec-nr/authwit" } | ||
ecdsa_public_key_note = { path = "../ecdsa_public_key_note" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
noir-projects/noir-contracts/contracts/ecdsa_public_key_note/Nargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[package] | ||
name = "ecdsa_public_key_note" | ||
authors = ["aztec-labs"] | ||
compiler_version = ">=0.25.0" | ||
type = "lib" | ||
|
||
[dependencies] | ||
aztec = { path = "../../../aztec-nr/aztec" } |
File renamed without changes.
10 changes: 10 additions & 0 deletions
10
noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/Nargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[package] | ||
name = "ecdsa_r_account_contract" | ||
authors = [""] | ||
compiler_version = ">=0.25.0" | ||
type = "contract" | ||
|
||
[dependencies] | ||
aztec = { path = "../../../aztec-nr/aztec" } | ||
authwit = { path = "../../../aztec-nr/authwit" } | ||
ecdsa_public_key_note = { path = "../ecdsa_public_key_note" } |
73 changes: 73 additions & 0 deletions
73
noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Account contract that uses ECDSA signatures for authentication on random version of the p256 curve (to use with touchID). | ||
contract EcdsaRAccount { | ||
use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, PrivateContext, PrivateImmutable}; | ||
use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note; | ||
|
||
use dep::aztec::protocol_types::abis::call_context::CallContext; | ||
use dep::std; | ||
|
||
use dep::authwit::{ | ||
entrypoint::{app::AppPayload, fee::FeePayload}, account::AccountActions, | ||
auth_witness::get_auth_witness | ||
}; | ||
|
||
use dep::ecdsa_public_key_note::EcdsaPublicKeyNote; | ||
|
||
#[aztec(storage)] | ||
struct Storage { | ||
public_key: PrivateImmutable<EcdsaPublicKeyNote>, | ||
} | ||
|
||
// Creates a new account out of an ECDSA public key to use for signature verification | ||
#[aztec(private)] | ||
#[aztec(initializer)] | ||
fn constructor(signing_pub_key_x: [u8; 32], signing_pub_key_y: [u8; 32]) { | ||
let this = context.this_address(); | ||
let header = context.get_header(); | ||
let this_npk_m_hash = header.get_npk_m_hash(&mut context, this); | ||
// Not emitting outgoing for msg_sender here to not have to register keys for the contract through which we | ||
// deploy this (typically MultiCallEntrypoint). I think it's ok here as I feel the outgoing here is not that | ||
// important. | ||
|
||
let mut pub_key_note = EcdsaPublicKeyNote::new(signing_pub_key_x, signing_pub_key_y, this_npk_m_hash); | ||
storage.public_key.initialize(&mut pub_key_note).emit(encode_and_encrypt_note(&mut context, this, this)); | ||
} | ||
|
||
// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts | ||
#[aztec(private)] | ||
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) { | ||
let actions = AccountActions::init(&mut context, is_valid_impl); | ||
actions.entrypoint(app_payload, fee_payload); | ||
} | ||
|
||
#[aztec(private)] | ||
#[aztec(noinitcheck)] | ||
#[aztec(view)] | ||
fn verify_private_authwit(inner_hash: Field) -> Field { | ||
let actions = AccountActions::init(&mut context, is_valid_impl); | ||
actions.verify_private_authwit(inner_hash) | ||
} | ||
|
||
#[contract_library_method] | ||
fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { | ||
// Load public key from storage | ||
let storage = Storage::init(context); | ||
let public_key = storage.public_key.get_note(); | ||
|
||
// Load auth witness | ||
let witness: [Field; 64] = get_auth_witness(outer_hash); | ||
let mut signature: [u8; 64] = [0; 64]; | ||
for i in 0..64 { | ||
signature[i] = witness[i] as u8; | ||
} | ||
|
||
// Verify payload signature using Ethereum's signing scheme | ||
// Note that noir expects the hash of the message/challenge as input to the ECDSA verification. | ||
let outer_hash_bytes: [u8; 32] = outer_hash.to_be_bytes(32).as_array(); | ||
let hashed_message: [u8; 32] = std::hash::sha256(outer_hash_bytes); | ||
let verification = std::ecdsa_secp256r1::verify_signature(public_key.x, public_key.y, signature, hashed_message); | ||
assert(verification == true); | ||
|
||
true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { type NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js'; | ||
|
||
import EcdsaKAccountContractJson from '../../../artifacts/EcdsaKAccount.json' assert { type: 'json' }; | ||
|
||
export const EcdsaKAccountContractArtifact = loadContractArtifact(EcdsaKAccountContractJson as NoirCompiledContract); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/** | ||
* The `@aztec/accounts/ecdsa` export provides an ECDSA account contract implementation, that uses an ECDSA private key for authentication, and a Grumpkin key for encryption. | ||
* Consider using this account type when working with integrations with Ethereum wallets. | ||
* | ||
* @packageDocumentation | ||
*/ | ||
import { AccountManager, type Salt } from '@aztec/aztec.js/account'; | ||
import { type AccountWallet, getWallet } from '@aztec/aztec.js/wallet'; | ||
import { type PXE } from '@aztec/circuit-types'; | ||
import { type AztecAddress, type Fr } from '@aztec/circuits.js'; | ||
|
||
import { EcdsaKAccountContract } from './account_contract.js'; | ||
|
||
export { EcdsaKAccountContractArtifact } from './artifact.js'; | ||
export { EcdsaKAccountContract }; | ||
|
||
/** | ||
* Creates an Account that relies on an ECDSA signing key for authentication. | ||
* @param pxe - An PXE server instance. | ||
* @param secretKey - Secret key used to derive all the keystore keys. | ||
* @param signingPrivateKey - Secp256k1 key used for signing transactions. | ||
* @param salt - Deployment salt. | ||
*/ | ||
export function getEcdsaKAccount(pxe: PXE, secretKey: Fr, signingPrivateKey: Buffer, salt?: Salt): AccountManager { | ||
return new AccountManager(pxe, secretKey, new EcdsaKAccountContract(signingPrivateKey), salt); | ||
} | ||
|
||
/** | ||
* Gets a wallet for an already registered account using ECDSA signatures. | ||
* @param pxe - An PXE server instance. | ||
* @param address - Address for the account. | ||
* @param signingPrivateKey - ECDSA key used for signing transactions. | ||
* @returns A wallet for this account that can be used to interact with a contract instance. | ||
*/ | ||
export function getEcdsaKWallet(pxe: PXE, address: AztecAddress, signingPrivateKey: Buffer): Promise<AccountWallet> { | ||
return getWallet(pxe, address, new EcdsaKAccountContract(signingPrivateKey)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,2 @@ | ||
/** | ||
* The `@aztec/accounts/ecdsa` export provides an ECDSA account contract implementation, that uses an ECDSA private key for authentication, and a Grumpkin key for encryption. | ||
* Consider using this account type when working with integrations with Ethereum wallets. | ||
* | ||
* @packageDocumentation | ||
*/ | ||
import { AccountManager, type Salt } from '@aztec/aztec.js/account'; | ||
import { type AccountWallet, getWallet } from '@aztec/aztec.js/wallet'; | ||
import { type PXE } from '@aztec/circuit-types'; | ||
import { type AztecAddress, type Fr } from '@aztec/circuits.js'; | ||
|
||
import { EcdsaAccountContract } from './account_contract.js'; | ||
|
||
export { EcdsaAccountContractArtifact } from './artifact.js'; | ||
export { EcdsaAccountContract }; | ||
|
||
/** | ||
* Creates an Account that relies on an ECDSA signing key for authentication. | ||
* @param pxe - An PXE server instance. | ||
* @param secretKey - Secret key used to derive all the keystore keys. | ||
* @param signingPrivateKey - Secp256k1 key used for signing transactions. | ||
* @param salt - Deployment salt. | ||
*/ | ||
export function getEcdsaAccount(pxe: PXE, secretKey: Fr, signingPrivateKey: Buffer, salt?: Salt): AccountManager { | ||
return new AccountManager(pxe, secretKey, new EcdsaAccountContract(signingPrivateKey), salt); | ||
} | ||
|
||
/** | ||
* Gets a wallet for an already registered account using ECDSA signatures. | ||
* @param pxe - An PXE server instance. | ||
* @param address - Address for the account. | ||
* @param signingPrivateKey - ECDSA key used for signing transactions. | ||
* @returns A wallet for this account that can be used to interact with a contract instance. | ||
*/ | ||
export function getEcdsaWallet(pxe: PXE, address: AztecAddress, signingPrivateKey: Buffer): Promise<AccountWallet> { | ||
return getWallet(pxe, address, new EcdsaAccountContract(signingPrivateKey)); | ||
} | ||
export * from './ecdsa_k/index.js'; | ||
export * from './ssh_ecdsa_r/index.js'; |
88 changes: 88 additions & 0 deletions
88
yarn-project/accounts/src/ecdsa/ssh_ecdsa_r/account_contract.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { type AuthWitnessProvider } from '@aztec/aztec.js/account'; | ||
import { AuthWitness, type CompleteAddress } from '@aztec/circuit-types'; | ||
import { EcdsaSignature } from '@aztec/circuits.js/barretenberg'; | ||
import { type ContractArtifact } from '@aztec/foundation/abi'; | ||
import { type Fr } from '@aztec/foundation/fields'; | ||
|
||
import { DefaultAccountContract } from '../../defaults/account_contract.js'; | ||
import { signWithAgent } from '../../utils/ssh_agent.js'; | ||
import { EcdsaRAccountContractArtifact } from './artifact.js'; | ||
|
||
const secp256r1N = 115792089210356248762697446949407573529996955224135760342422259061068512044369n; | ||
/** | ||
* Account contract that authenticates transactions using ECDSA signatures | ||
* verified against a secp256r1 public key stored in an immutable encrypted note. | ||
* Since this implementation relays signatures to an SSH agent, we provide the | ||
* public key here not for signature verification, but to identify actual identity | ||
* that will be used to sign authwitnesses. | ||
*/ | ||
export class EcdsaRSSHAccountContract extends DefaultAccountContract { | ||
constructor(private signingPublicKey: Buffer) { | ||
super(EcdsaRAccountContractArtifact as ContractArtifact); | ||
} | ||
|
||
getDeploymentArgs() { | ||
return [this.signingPublicKey.subarray(0, 32), this.signingPublicKey.subarray(32, 64)]; | ||
} | ||
|
||
getAuthWitnessProvider(_address: CompleteAddress): AuthWitnessProvider { | ||
return new SSHEcdsaRAuthWitnessProvider(this.signingPublicKey); | ||
} | ||
} | ||
|
||
/** Creates auth witnesses using ECDSA signatures. */ | ||
class SSHEcdsaRAuthWitnessProvider implements AuthWitnessProvider { | ||
constructor(private signingPublicKey: Buffer) {} | ||
|
||
#parseECDSASignature(data: Buffer) { | ||
// Extract ECDSA signature components | ||
let offset = 0; | ||
const sigTypeLen = data.readUInt32BE(offset); | ||
offset += 4; | ||
const sigType = data.subarray(offset, offset + sigTypeLen).toString(); | ||
offset += sigTypeLen; | ||
|
||
if (sigType !== 'ecdsa-sha2-nistp256') { | ||
throw new Error(`Unexpected signature type: ${sigType}`); | ||
} | ||
|
||
offset += 4; | ||
const rLen = data.readUInt32BE(offset); | ||
offset += 4; | ||
let r = data.subarray(offset, offset + rLen); | ||
offset += rLen; | ||
|
||
const sLen = data.readUInt32BE(offset); | ||
offset += 4; | ||
let s = data.subarray(offset, offset + sLen); | ||
|
||
// R and S are encoded using ASN.1 DER format, which may include a leading zero byte to avoid interpreting the value as negative | ||
if (r.length > 32) { | ||
r = Buffer.from(Uint8Array.prototype.slice.call(r, 1)); | ||
} | ||
|
||
if (s.length > 32) { | ||
s = Buffer.from(Uint8Array.prototype.slice.call(s, 1)); | ||
} | ||
|
||
const maybeHighS = BigInt(`0x${s.toString('hex')}`); | ||
|
||
// ECDSA signatures must have a low S value so they can be used as a nullifier. BB forces a value of 27 for v, so | ||
// only one PublicKey can verify the signature (and not its negated counterpart) https://ethereum.stackexchange.com/a/55728 | ||
if (maybeHighS > secp256r1N / 2n + 1n) { | ||
s = Buffer.from((secp256r1N - maybeHighS).toString(16), 'hex'); | ||
} | ||
|
||
return new EcdsaSignature(r, s, Buffer.from([0])); | ||
} | ||
|
||
async createAuthWit(messageHash: Fr): Promise<AuthWitness> { | ||
// Key type and curve name | ||
const keyType = Buffer.from('ecdsa-sha2-nistp256'); | ||
const curveName = Buffer.from('nistp256'); | ||
const data = await signWithAgent(keyType, curveName, this.signingPublicKey, messageHash.toBuffer()); | ||
const signature = this.#parseECDSASignature(data); | ||
|
||
return new AuthWitness(messageHash, [...signature.r, ...signature.s]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { type NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js'; | ||
|
||
import EcdsaRAccountContractJson from '../../../artifacts/EcdsaRAccount.json' assert { type: 'json' }; | ||
|
||
export const EcdsaRAccountContractArtifact = loadContractArtifact(EcdsaRAccountContractJson as NoirCompiledContract); |
Oops, something went wrong.