Josh
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Minimal arbitrarily generic attestations (MAGA) MAGA is a simple on-chain, privacy preserving attestations protocol. Attestations are associated with an ECDSA signature hash that a user can verify ownership of with a zk proof. [Github repo here](https://github.com/critesjosh/private-attesations). This may be best for off chain applications. consider making the time a Public input that invalidates it after X seconds. the solidity contract could be adapted for specific onchain applications, to make it resistant to front-running the proof public inputs must include any inputs to other solidity functions in the transaction. so for access control, the proof must include inputs for the action to be taken. eg, if its for a vote, the proof must include the vote (for, against), the proposal id and contract, otherwise those could be manipulated by a front runner. Recursive proofs greatly expand the design space of what's possible. The multisig and voting use cases explored at the bottom of this document are improved greatly when using recursive proofs for aggregation. ## Identifiers An unlimited number of identifiers can be generated from a single master seed, similar to how [HD wallets work](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki). The user would generate their master seed by signing a message with an ethereum wallet. This message should be standardized, so different applications generate identifiers from the same seed. The `x` ($x_0$) of the pedersen hash of the seed will be the first identifier, $i_0$. $(x_0, y_0) = pedersen(ecdsa\_signature)$ $i_0 = x_0$ $(x_1, y_1) = pedersen([x_0, y_0])$ $i_1 = x_1$ $(x_2, y_2) = pedersen([x_1, y_1])$ $i_2 = x_2$ ```rs! fn pedersen(_input : [Field]) -> [Field; 2] ``` This method has the benefit of not requiring a trusted 3rd party service provider to provide a good UX. A user can generate as many identifiers as the want from a single ECDSA signature and it can all be done client side, in the browser. This requires a slightly more complex circuit. The circuit will need to have an input that indicates how many times to hash the master seed to return the correct identifier. **Update:** the number of loop iterations must be determined at compile time, so you can't specify the number of times to hash as an input. As a workaround, we can create several circuits that all do the same thing and just loop different number of times (10, 20, 50, 100, etc) depending on what the user needs. The loop will build an array of the users' identifiers. The user will pass the index for the identifier that they want to check against. See the circuit below for details. ## Attestations Attestations are stored in an array of `string`s. It is up to the dapp/attester to encode data in the string however they please. consider [base64](https://github.com/Vectorized/solady/blob/8d868a936ec1a45be294e26de1a64ebfb73c6c20/src/utils/Base64.sol). Base64 encoding reduces storage costs, but increases compute costs for decoding base64, need to test at what storage size base64 becomes more cost effective. They may also want to bind the attestation to a specific `identifier` by including the `identifier` in the attestation data, so it can't be copied and added to another `identifier`. Users can have unlimited attestations associated with a specific hash, but the more attestations that are collected at one hash, the less anonymous they are. Associations between attestations leaks information. ### Attestation structure It would be useful to store these structures as events emitted on chain. #### Schemas Two types of schema: - message schema - signature schema Schemas are emitted as events when an attestation is added. ```solidity event EmitSchema(string schema); ``` Example: ```solidity emit EmitSchema("address,uint16,bytes32"); ``` #### Example schema Suggested standard encoding for an attestation. Adapt as needed. | variable | data type | location (bytes index) | description | |---|---|--- | -- | | `r` | `bytes32 `| 0-31 | Only required if this is a signed attestion. Users will likely want signed attestations from credible accounts to make the attestation more meaningful. | | `s` | `bytes32` | 32-63 | | `v` | `uint8` | 64 | | `message` | `string` | 65+ | Include arbitrary data that may need to be passed as public proof inputs to a `verifier.verify()` call for replay protection. identifier info should go in here. | ### Message structure This will vary per use case. For example, for a multisig, the attester will likely want to link the attestation to the identifier and the multisig wallet address. There are message components that will likely be in most/every attestation: - Identifier - MAGA protocol version number / schema ## Solidity Contract The expected identifier is always the first public input. This contract can be used for different circuits, the only expectation is that the identifier is the first public input. Adding additional `publicInputs` to the circuit does not change the interface, so `verifyAttestation` should work with many different verifier contracts. ### Interface ```solidity interface IMaga { // processes batches of attestations function attest( bytes32[] memory _identifiers, bytes[][] memory _attestations) public {} // deletes an attestation // only callable by attesation signers function revokeAttestations( bytes32[] memory _identifiers, uint8[][] memory _attestationIndexes, bytes32[][] memory _messageHashes // calculate off-chain for simplicity ) public {} // _publicInputs[0] - _indentifier function verifyAttestation( bytes memory _proof, bytes32[] memory _publicInputs, uint _attestationIndex) public view returns (bytes memory) {} } ``` ### Implementation ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {UltraVerifier} from '../circuits/contract/plonk_vk.sol'; contract Maga { UltraVerifier verifier; mapping(bytes32 => bytes[]) attestations; constructor(address _verifier){ verifier = UltraVerifier(_verifier); } // possible to batch attestations function attest( bytes32[] memory _identifiers, bytes[][] memory _attestations) public { for (uint i = 0; i < _identifiers.length; i++) { for (uint j = 0; j < _attestations[i].length; j++) { // push all of the attestations onto the identifier attestations[_identifiers[i]].push(_attestations[i][j]); } } } // This function will only work for attestations that // have an associated signature. The attestation can only be revoked // by the account that issued it. // Can also batch these calls // This function assumes that signatures are located // in the bytes locations discussed (in #attestation-structure) above. function revokeAttestations( bytes32[] memory _identifiers, uint8[][] memory _attestationIndexes, bytes32[][] memory _messageHashes // calculate off-chain for simplicity ) public { for (uint i = 0; i < _identifiers.length; i++) { for (uint j = 0; j < _attestationIndexes[i].length; j++) { uint8 index = _attestationIndexes[i][j]; bytes memory attestation = attestations[_identifiers[i]][index]; uint8 v = uint8(subset(attestation, 128, 0)[0]); bytes32 r; bytes32 s; bytes memory rBytes = subset(attestation, 64, 32); bytes memory sBytes = subset(attestation, 96, 32); assembly { r := mload(add(rBytes, 32)) s := mload(add(sBytes, 32)) } require(msg.sender == ecrecover(_messageHashes[i][j], v, r, s)); delete attestations[_identifiers[i]][index]; } } } // _publicInputs[0] - _indentifier function verifyAttestation( bytes memory _proof, bytes32[] memory _publicInputs, uint _attestationIndex) public view returns (bytes memory) { verifier.verify(_proof, _publicInputs); return attestations[_publicInputs[0]][_attestationIndex]; } function verifyAttestationFromSafe( bytes memory _proof, bytes32[] memory _publicInputs, uint _attestationIndex) public view returns (bytes memory) { // requires a different circuit that takes // multiple ECDSA signatures (from the safe account signers) // the circuit // 1. takes an array of signatures // 2. gets the signer addresses for the sigs // 3. proves that the signers are owners of the safe // 4. proves there are >= the required signatures // to meet the multisig threshold // Waiting on ethereum storage proofs in noir // to implement this. return attestations[_publicInputs[0]][_attestationIndex]; } // helper function function subset(bytes memory _b, uint _startIndex, uint _length) pure internal returns(bytes memory) { bytes memory newSet = new bytes(_length); for (uint i = 0; i < _length; i++) { newSet[i] = _b[_startIndex + i]; } return newSet; } } ``` ## Circuit ```rust use dep::std; // We must know how many identifiers to generate at compile time. // starting with 20 as an arbitrary number global NUM_IDENTIFIERS = 20; // identifier_index should be even since we are getting the 'x' output of flattened array of [x,y] values // keep identifier as the first input so its always at _publicInputs[0] in the Solidity contract fn main(identifier : pub Field, identifier_index : Field, pub_key_x : [u8; 32], pub_key_y : [u8; 32], signature: [u8; 64], message_hash : [u8; 32] ) { let isValid = std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, message_hash); assert(isValid == 1); let mut signature_as_field: [Field; 64] = [0;64]; for i in 0..signature_as_field.len() { signature_as_field[i] = signature[i] as Field; } let master_seed = std::hash::pedersen(signature_as_field); // flattened array of 20 [Field; 2] elements let mut identifiers: [Field; NUM_IDENTIFIERS*2] = [0;NUM_IDENTIFIERS*2]; identifiers[0] = master_seed[0]; identifiers[1] = master_seed[1]; let mut target_identifier = 0; if(identifier_index > 0) { for i in 0..20 { // if the target has been found, skip future iterations if(target_identifier == 0){ // multiply by 2 since we are working with a flattened array let j = i * 2; // new identifier is the hash of the previous let prev_identifier = [identifiers[j-2], identifiers[j-1]]; // hash the previous identifier to get the new identifier let new_identifier = std::hash::pedersen(prev_identifier); identifiers[j] = new_identifier[0]; identifiers[j+1] = new_identifier[1]; if(identifier == identifiers[j]){ target_identifier = identifiers[j]; assert(target_identifier == identifier); } } } } else { assert(identifiers[0] == identifier); } } ``` ## Use cases ### Anon multisig use identifiers as access control for a multisig. this requires Recursive proofs to work well. it also requires a new circuit specifically for the multisig use case, where signers indicate the tx details as Public inputs to the circuit. in the circuit, attestation holders prove they hold the proper attestation (with the multisig address in the signed attestation message) and indicate the tx destination, amount and calldata. these proofs are shared among signers or posted somewhere for availability. once the threshold number of proofs is available, someone can take those and prove that all of the attestion holders are authorized to sign for the multisig. - inputs to the circuit - array of identifiers (could be private? but not helpful) - contract address for multisig - destination - value - calldata ### Voting similar idea as above with recursion. this is a bit more difficult because there are censorship concerns. is there a way to show proof of tally while maintaining privacy and allowing censored votes to be force included on L1? circuit would have to aggregate all of the vote proofs, for/against totals would be public inputs. if someone is able to sumbit a valid tally proof with a higher total vote count, the previous submitter is slashed and total is invalid. or there is a token incentive for someone to sumbit the tally proof. the window for tally proofs is X days. only the highest vote counr tally proof gets the reward. this incentives people to withhold votes and create valid proofs later. is there a way to timesamp them? maybe just follow the [snapshot pattern](https://blog.ipfs.tech/2022-08-25-snapshot-ipfs-case-study/#how-snapshot-uses-ipfs) and just put them all of IPFS. snapshot actually has a lot of the same problems as this, so research how they do it (closing votes, allowing people to vote twice, etc). Maybe reach out and see if there is capability for privacy preserving extension to snapshot ([plugin](https://docs.snapshot.org/user-guides/plugins)). Once we have storage proofs in Noir, voters could prove that they hold a certain number of tokens while casting a ballot and get increased voting weight. There are privacy concerns with this approach. If I have >100 tokens and only 2 other people do as well, if I vote with my full weight, it will be public knowledge that 1 of the 3 of us voted this way. The circuit should allow a user to choose how much to vote with. There might be some way to let people vote in chunks, to be explored further.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully