
Iden3 Documentation Site¶
Iden3 is an open source project offering a complete decentralized identity management solution over the public blockchain. It is designed with four fundamental goals in mind:
Enable Self Sovereign Identities, whereby identities are created, owned and controlled by the user.
Accesibility, by promoting the development of open standards and ease of use.
Scalability, by minimizing the number of accesses to the blockchain that identity management and claim processing requires.
Privacy by design by using including zkSNARKs to prove claims whithout revealing any information other than the validity of the claim.
Note
Our team can always use your feedback and help to improve the tutorials and material included. If you don’t understand something, or cannot find what you are looking for, or have any suggestion, help us make the documentation better by letting us know!
Submit an issue or pull request on the GitHub repository
This documentation site includes the following sections:
- Technology : provides a description of iden3’s technology.
- Developers : guide on how to integrate iden3’s software for specific applications.
- Repository Guide : centralizes documentation scattered across different iden3’s repos in a single place.
- Publications : includes articles and papers authored by iden3.
During the comming months, we will be sorting, improving and extending the documentation, so don’t forget to come back. Thank you!!!
Services & Protocols¶
Login Protocol¶
Contents
Login protocol¶
The login protocol is based on the signature protocol, in which a user signs a packet using an authorized kSign key.
For the login case, the user desires to assert a particular identity (an ethereum address in this case) to a server so that they are allowed access into the service while being identified.
Assumptions¶
- Secure connection between Wallet and Server.
- Secure connection between Web Client and Server.
- Wallet authenticates the Server in the connection.
- Web Client authenticates the Server in the connection.
What is needed¶
- Server authenticates the Ethereum Address and Ethereum Name from the Wallet.
- The user transfers the authentication from the Wallet to the Web Client.
Protocol flow¶
Challenges contain a cryptographic nonce and have a timeout that indicates the validity of the nonce in the challenge. A signed challenge with a timed out nonce must be rejected by the server. The server must store a list of not timed out nonces that haven’t been signed yet to guarantee freshness.
A cryptographic nonce must be securely generated and long enough to avoid colisions (we use 256 bits).
Signature Protocol v0.1 spec¶
A signature may be requested as follows:
{
header: {
typ: iden3.sig.v0_1
}
body: {
type: TYPE
data: DATA
}
}
The user will generate a packet following the signature protocol
specification, that may contain data from a signature request, or may be
made from scratch. The packet contains a header and a payload, and is
serialized and signed following the JWS
standard. Usually the form
will be filled by the user, and data
will be copied from a request.
The structure of the data
and form
in the payload are specified
by the type
(what is being signed) in the payload. The rest of the
elements are specified by the typ
(signature packet) in the header.
JWS_PAYLOAD = {
type: TYPE
data: DATA
form: FORM
ksign: str # ksing public key in compressed form
proofKSing: proofClaim # Proof of authorize k sign claim (which contains the public key in compressed form)
}
JWS_HEADER = {
typ: iden3.sig.v0_1
iss: str # Ethereum Address
iat: uint # issued at time, unix timestamp
exp: uint # expiration time, unix timestamp
alg: ? # algorithm
}
JWS_SIGN(JWS_HEADER, JWS_PAYLOAD)
Each Signature request type
has a view representation for the user,
where the data
and form
are presented. Some of the values may be
hidden from the user when necessary, but only if doing so doesn’t
compromise the security of the user. In the request view, the user has
the ability to pick some elements of the form
.
ksign
is the compressed public key of a secp256k ECDSA key pair. The
proofKSing
contains a KSign Authorize Claim for a secp256k public
key.
As JWS_HEADER.alg
we will use a custom algorithm (not defined in the
JWS standard): “EK256K1”, which is ECDSA with secp256k1 curve and keccak
as hash function, the same signature algorithm configuration used in
Ethereum.
Auxiliary data structures¶
proofClaim: {
signature: signature # Relay root + date signed by relay
date: uint
leaf: claim
proofs: proofClaimPartial[]
}
proofClaimPartial: {
mtp0: mtp # merkle tree proof of leaf existence
mtp1: mtp # merkle tree proof of leaf non-existence
root: key # merkle tree root
aux: nil | { ver: uint, era: uint, idAddr: str } # Necessary data to construct SetRootClaim from root
}
Usually the relay returns the proofClaim
data structure to prove
that a claim is valid and is in the merkle tree.
Identity Assertion v0.1 spec¶
payload:
type: iden3.iden_assert.v0_1
data: {
challenge: nonce # 256 bits in base64
timeout: uint # seconds
origin: str # domain
}
form: {
ethName: str # ethereumName
proofAssignName: proofClaim # proof of claim Assign Name for ethName
}
A session id, if necessary, can be computed from the challenge. This session id can be used to link the communication between the web service and the wallet service.
view:
type: Identity Assertion
data: {
origin: str # domain
}
form: {
ethName: str # ethereum name
}
Algorithms¶
Here we show an overview of the algorithms steps used for verification
of the proofs and signatures used in the login protocol. The following
algorithms consider the case in which there is a only a single trusted
entity (identified by relayPk
) that acts as a relay and as a domain
name server.
Signature verification algorithm¶
VerifySignedPacket(jwsHeader, jwsPayload, signature, relayPk):
1. Verify jwsHeader.typ is 'iden3.sig.v0_1'
2. Verify jwsHeader.alg is 'EK256K1'
3. Verify that jwsHeader.iat <= now() < jwsHeader.exp
4. Verify that jwsPayload.ksign is in jwsPayload.proofKSign.leaf
5. Verify that jwsHeader.iss is in jwsPayload.proofKSign
6. Verify that signature of JWS(jwsHeader, jwsPayload) by jwsPayload.ksign is signature
7. VerifyProofOfClaim(jwsPayload.proofKSign, relayPk)
In 4. we verify that the ksign used to sign the packet is authorized by the user, identified by jwsHeader.iss ethereum address.
Iden Assert verification algorithm¶
VerifyIdenAssertV01(nonceDB, origin, jwsHeader, jwsPayload, signature, relayPk):
1. Verify jwsPayload.type is 'iden3.iden_assert.v0_1'
2. Verify jwsPayload.data.origin is origin
3. Verify jwsPayload.data.challenge is in nonceDB and hasn't expired, delete it
4. Verify that jwsHeader.iss and jwsPayload.form.ethName are in jwsPayload.proofAssignName.leaf
5. VerifyProofOfClaim(jwsPayload.form.ethName, relayPk)
ProofOfClaim verification¶
VerifyProofOfClaim(p, relayPk):
1. Verify signature of p.proofs[-1].root by relayPk is p.signature
let leaf = p.leaf
2. loop for each proof in p.proofs:
2.1 Verify proof.mtp0 is existence proof
2.2 Verify proof.mtp0 with leaf and proof.root
2.3 Verify proof.mtp1 is non-existence proof
2.4 Verify proof.mtp1 with ClaimIncrementVersion(leaf) and proof.root
leaf = NewClaimSetRootClaim(p.root, p.aux.ver, p.aux.era, p.aux.ethAddr)
Rationale¶
See this document for the rationale of some decisions made in the design of this protocol.
iden3js - protocols¶
Login (Identity Assertion)¶
Wallet Service
+ +
| signatureRequest |
| <-------------------------------------+ |
| |
| +---+ |
| | |
| |sign packet |
| | |
| <---+ |
| signedPacket |
| +-------------------------------------> |
| |
| +---+ |
| verify | |
| signedPacket| |
| | |
| +---> |
| |
| ok |
| <-------------------------------------+ |
| |
| |
| |
+ +
Read the login protocol specification here.
Define new NonceDB¶
const nonceDB = new iden3.protocols.NonceDB();
Generate New Request of Identity Assert¶
input
nonceDB
: NonceDB class objectorigin
: domain of the emitter of the requesttimeout
: unixtime format, valid until that date. We can use for example 2 minutes (2*60
seconds)
output
signatureRequest
:Object
const signatureRequest = iden3.protocols.login.newRequestIdenAssert(nonceDB, origin, 2*60);
The nonce
of the signatureRequest
can be getted from:
const nonce = signatureRequest.body.data.challenge;
// nonce is the string containing the nonce value
We can add auxiliar data to the nonce
in the nonceDB
only one
time:
const added = nodeDB.addAuxToNonce(nonce, auxdata);
// added is a bool confirming if the aux data had been added
Sign Packet¶
input
signatureRequest
: object generated in thenewRequestIdenAssert
functionuserAddr
: Eth Address of the user that signs the data packetethName
: name assigned to theuserAddr
proofOfEthName
:proofOfClaim
of theethName
kc
: iden3.KeyContainer objectksign
: KOperational authorized for theuserAddr
proofOfKSign
:proofOfClaim
of theksign
expirationTime
: unixtime format, signature will be valid until that date
output
signedPacket
:String
const expirationTime = unixtime + (3600 * 60); const signedPacket = iden3.protocols.login.signIdenAssertV01(signatureRequest, usrAddr, ethName, proofOfEthName, kc, ksign, proofOfKSign, expirationTime);
Verify Signed Packet¶
input
nonceDB
: NonceDB class objectorigin
: domain of the emitter of the requestsignedPacket
: object generated in thesignIdenAssertV01
function
output
nonce
: nonce object of the signedPacket, that has been just deleted from the nonceDB when the signedPacket is verified. If the verification fails, the nonce will beundefined
const verified = iden3.protocols.login.verifySignedPacket(nonceDB, origin, signedPacket);
Apendix¶
See the login specification document for information about the protocol design.
Rationale¶
The following document contains references to similar protocols on which our login protocol relies on or takes inspiration from.
Signature format¶
Use JSON to encode the object that will be signed.
JSON Signing formats¶
https://medium.facilelogin.com/json-message-signing-alternatives-897f90d411c
- JSON Web Signature (JWS)
- Doesn’t need canonicalization
- Allows signing arbitrary data (not only JSON)
- Widely used
- JSON Cleartext Signature (JCS)
- Concise Binary Object Representation (CBOR) Object Signing
https://matrix.org/docs/spec/appendices.html#signing-json
- Matrix JSON Signing
- Allows having multiple signatures with different protocols for a single JSON
Possible attacks¶
See WebAuth API, FIDO Threat analysis
References¶
- https://en.wikipedia.org/wiki/OpenID
- https://en.wikipedia.org/wiki/OpenID_Connect
- https://en.wikipedia.org/wiki/IndieAuth
- https://fidoalliance.org/how-fido-works/
WebAuth API¶
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API
- https://w3c.github.io/webauthn/
- https://www.w3.org/TR/webauthn/
Demo: - https://www.webauthn.org/
FIDO Security guarantees and how they are achieved: - https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-security-ref-v2.0-id-20180227.html#relation-between-measures-and-goals - FIDO Threat analysis and mitigations: - https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-security-ref-v2.0-id-20180227.html#threat-analysis
Currently (2018-01-08) there’s no support for iOS (Safari): - https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API#Browser_compatibility
Criticism: - https://www.scip.ch/en/?labs.20180424
Example code of server verification: - https://github.com/duo-labs/webauthn/blob/fa6cd954884baf24fc5a51656ce21c1a1ef574bc/main.go#L336 - https://w3c.github.io/webauthn/#verifying-assertion
Apendix¶
The FIDO protocols security goals:¶
Strong User Authentication: Authenticate (i.e. recognize) a user and/or a device to a relying party with high (cryptographic) strength. #### [SG-2] Credential Guessing Resilience: Provide robust protection against eavesdroppers, e.g. be resilient to physical observation, resilient to targeted impersonation, resilient to throttled and unthrottled guessing. #### [SG-3] Credential Disclosure Resilience: Be resilient to phishing attacks and real-time phishing attack, including resilience to online attacks by adversaries able to actively manipulate network traffic. #### [SG-4] Unlinkablity: Protect the protocol conversation such that any two relying parties cannot link the conversation to one user (i.e. be unlinkable). #### [SG-5] Verifier Leak Resilience: Be resilient to leaks from other relying parties. I.e., nothing that a verifier could possibly leak can help an attacker impersonate the user to another relying party. #### [SG-6] Authenticator Leak Resilience: Be resilient to leaks from other FIDO Authenticators. I.e., nothing that a particular FIDO Authenticator could possibly leak can help an attacker to impersonate any other user to any relying party. #### [SG-7] User Consent: Notify the user before a relationship to a new relying party is being established (requiring explicit consent). #### [SG-8] Limited PII: Limit the amount of personal identifiable information (PII) exposed to the relying party to the absolute minimum. #### [SG-9] Attestable Properties: Relying Party must be able to verify FIDO Authenticator model/type (in order to calculate the associated risk). #### [SG-10] DoS Resistance: Be resilient to Denial of Service Attacks. I.e. prevent attackers from inserting invalid registration information for a legitimate user for the next login phase. Afterward, the legitimate user will not be able to login successfully anymore. #### [SG-11] Forgery Resistance: Be resilient to Forgery Attacks (Impersonation Attacks). I.e. prevent attackers from attempting to modify intercepted communications in order to masquerade as the legitimate user and login to the system. #### [SG-12] Parallel Session Resistance: Be resilient to Parallel Session Attacks. Without knowing a user’s authentication credential, an attacker can masquerade as the legitimate user by creating a valid authentication message out of some eavesdropped communication between the user and the server. #### [SG-13] Forwarding Resistance: Be resilient to Forwarding and Replay Attacks. Having intercepted previous communications, an attacker can impersonate the legal user to authenticate to the system. The attacker can replay or forward the intercepted messages. #### [SG-14] (not covered by U2F) Transaction Non-Repudiation: Provide strong cryptographic non-repudiation for secure transactions. #### [SG-15] Respect for Operating Environment Security Boundaries: Ensure that registrations and private key material as a shared system resource is appropriately protected according to the operating environment privilege boundaries in place on the FIDO user device. #### [SG-16] Assessable Level of Security: Ensure that the design and implementation of the Authenticator allows for the testing laboratory / FIDO Alliance to assess the level of security provided by the Authenticator.
Claim Server¶
Identity Management System¶
Name Server¶
Notification Server¶
Relayer¶
Wallet¶
Centralized Login Use Case¶
Overview
This document will guide you through the steps required to integrate iden3’s technology into your application’s login.
Date: | 2019-04-05 |
---|
Contents
Introduction¶
Iden3 is a complete decentralized identity management solution that allows users to leverage their pre-existing validated identities to proof they are who they claim to be, saving them the hassle of having to individually register with each service that requires a validated identification. One of the direct applications of iden3’s technology is to allow web services to reuse these identities for login into their portals.
The diagram below shows the steps taken by your back-end to enable access to your application using iden3 identity system once the user requests to login.
Iden3 provides the SDK to take care of requesting and verifying identity so that the user can be authenticated.
Platforms supported¶
JavaScript: | https://github.com/iden3/iden3js |
---|---|
Go: | https://github.com/iden3/go-iden3 |
Pre-requirements¶
Minimum requirements for a functional centralized login include:
- iden3 wallet service has been deployed
- User attempting to login has at least one valid identity.
Integration¶
A JavaScript reference implementation of how a third party can integrate iden3’s solution to login into their application can be found at https://github.com/iden3/centralized-login-demo. In this example the external service includes a front-end and a back-end server. We will assume this front-end/back-end division during our integration overview.
Some examples on how the Go implementation is used can be found at https://github.com/iden3/go-iden3/blob/master/services/signedpacketsrv/signedpacket_test.go
Front-End¶
On the front-end side you will typically need to embed a button to start the login process, and a place to display a QR code that the user can scan to complete the authentication. After the button is pressed, the front-end makes a request to the back-end to start the identity authentication and waits for the response containing the QR code to be displayed and scanned by the user.
In the provided reference implementation this is achieved by JavaScript function getLoginData() found in frontend/index.js. This code shows how to :
- Send a request for a new requestIdenAssert packet to the centralized application back-end
- Open a websocket between front-end and back-end
- Display QR code containing the requestIdenAssert packet to be signed by iden3’s wallet
Back-End¶
Generating requests of identity assertion¶
On the back-end side you will need to prepare a new API endpoint to handle the requestIdenAssert() petitions from the front-end. In the reference implementation we use GET/login by calling JavaScript function
const signatureRequest = iden3.protocols.login.newRequestIdenAssert(nonceDB, origin, timeout);
or Go function:
requestIdenAssert := NewRequestIdenAssert(nonceDb, origin, timeout)
where
- nonceDB: is a NonceDB object generated by calling an API function and stored in a RAM database
- origin: domain of the emitter of the request, for example ‘myweb.com’
- timeout: timeout in seconds, for example 2 minutes (120).
nonceDB is obtained by calling the following JavaScript function:
const nonceDB = new iden3.protocols.NonceDB();
or Go function:
Once you have the signatureRequest object, you can return it back to the front-end so that it can be displayed.
Verifying signedPacket¶
On the back-end you will also need to prepare a new API endpoint to handle the responses from iden3 wallet containing the signedPacket. In the reference implementation we use POST /login to allow the walled to send the signed data.
To perform the verification in the newly added endpoint you just need to call iden3js library:
const verified = iden3.protocols.login.verifySignedPacket(nonceDB, origin, signedPacket);
or go-iden3 library:
verified, err := signedPacketVerifier.
VerifySignedPacketIdenAssert(signedPacket, nonceDB, origin)
where
- nonceDB: is the NonceDB object generated earlier.
- origin: domain of the emitter of the request, for example ‘myweb.com’
- signedPacket: signed packet sent by iden3’s wallet.
- verified: is null if verification fails.
Services and Infrastructure¶
Basic services and infrastructure libraries.
Repo | Description |
---|---|
iden3js | Javascript client library |
go-iden3 | Go implemenation of the iden3 system |
tx-forwarder | Implementation of server that pays gas on behalf of user signed transactions for specified smart contracts in ethereum blockchain |
discovery-node | Decentralized discovery protocol implementation |
notification-server | Implementation of a notification server to push notifications to user identity wallets |
Iden3js¶
Overview¶
Contents
iden3js¶
Javascript client library of the iden3 system.
Basic usage¶
// import iden3js
const iden3 = require('@iden3/iden3');
// Simulate local storage locally if no browser is used
// if (typeof localStorage === 'undefined' || localStorage === null) {
// const LocalStorage = require('node-localstorage').LocalStorage;
// localStorage = new LocalStorage('./tmp');
// }
// It should be noted that if no babel is installed on `package.json` dependencies,
// next dependencies should be add to `dependecies` section on `package.json`:
// "babel-plugin-transform-class-properties": "^6.24.1",
// "babel-plugin-transform-runtime": "^6.23.0",
// new database
const db = new iden3.Db();
// new key container using localStorage
const keyContainer = new iden3.KeyContainer('localStorage', db);
// unlock the KeyContainer for the next 30 seconds
let passphrase = 'pass';
keyContainer.unlock(passphrase);
// generate master seed
const mnemonic = 'enjoy alter satoshi squirrel special spend crop link race rally two eye';
keyContainer.generateMasterSeed(mnemonic);
// Generate keys for first identity
const keys = keyContainer.createKeys();
/*
keys: [
'0xc7d89fe96acdb257b434bf580b8e6eb677d445a9',
'0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833',
'0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91',
'0xb07079bd6238fa845dc77bbce3ec2edf98ffe735'
];
*/
// It should be noted that 'keys' are in form of ethereum addresses except
// key[1] that is a pubic key in its compressed form
let keyAddressOp = keys[0];
let keyPublicOp = keys[1];
let keyRecover = keys[2];
let keyRevoke = keys[3];
// For more info and details about mnemonic, see section Usage > KeyContainer
// create a new relay object
const relayAddr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c';
const relayUrl = 'http://127.0.0.1:8000/api/unstable';
const relay = new iden3.Relay(relayUrl);
// create a new id object
let id = new iden3.Id(keyPublicOp, keyRecover, keyRevoke, relay, relayAddr, '', undefined, 0);
// generates the counterfactoual contract through the relay, get the identity address as response
let proofKsign = {};
console.log('Create Identity');
id.createID()
.then((createIdRes) => {
// Successfull create identity api call to relay
console.log(createIdRes.idAddr); // Identity counterfactoual address
proofKsign = createIdRes.proofClaim;
console.log(proofKsign); // Proof of claim regarding authorization of key public operational
console.log('Create and authorize new key for address');
// generate new key from identity and issue a claim to relay in order to authorize new key
const keyLabel = 'testKey';
const newKey = id.createKey(keyContainer, keyLabel, true);
id.authorizeKSignSecp256k1(keyContainer, id.keyOperationalPub, newKey)
.then((authRes) => {
proofKSign = authRes.data.proofClaim;
console.log(proofKSign);
})
.catch((error) => {
console.error(error.message);
});
console.log('Bind label to an identity');
// bind the identity address to a label. It send required data to name-resolver service and name-resolver issue a claim 'assignName' binding identity address with label
const name = 'testName';
id.bindID(keyContainer, name)
.then( (bindRes) => {
console.log(bindRes.data);
// request idenity address to name-resolver ( currently name-resolver service is inside relay) from a given label
relay.resolveName(`${name}@iden3.io`)
.then((resolveRes) => {
const idAddr = resolveRes.data.idAddr;
console.log(`${name}@iden3.io associated with addres: ` + idAddr);
})
.catch((error) => {
console.error(error.message);
});
})
.catch((error) => {
console.error(error.message);
});
console.log('Deploy identity smart contract');
// creates identity smart contract on the ethereum blockchain testnet
id.deployID()
.then((deployIdRes) => {
// Successfull deploy identity api call to relay
console.log(deployIdRes.status);
})
.catch(() => {
// If identity is already deployed, throws an error
console.log('Identity already deployed');
});
})
.catch((error) => {
console.error(error.message);
});
Example can be found in
`iden3-basic-usage.example.js
<https://github.com/iden3/iden3js/blob/master/examples/iden3-basic-usage.example.js>`__
Centralized login¶
In the next links, one can be found an example of iden3
implementation as well as the login protocol explained in detail
https://github.com/iden3/iden3js/blob/master/src/protocols/README.md
Usage¶
const iden3 = require('iden3');
- new KeyContainer using localStorage
// new key container
// new database
const db = new iden3.Db();
let keyContainer = new iden3.KeyContainer('localStorage');
- usage:
// unlock the KeyContainer for the next 30 seconds
let passphrase = 'pass';
keyContainer.unlock(passphrase);
// generate master seed
const mnemonic = 'enjoy alter satoshi squirrel special spend crop link race rally two eye';
keyContainer.generateMasterSeed(mnemonic);
// Also, master seed can be generated randomly if no mnemonic is specified
// keyContainer.generateMasterSeed();
// functions above stores seed mnemonic into local storage
// it can be retrieved through:
const mnemonicDb = keyContainer.getMasterSeed();
// Generate keys for first identity
const keys = keyContainer.createKeys();
/*
keys: [
'0xc7d89fe96acdb257b434bf580b8e6eb677d445a9',
'0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833',
'0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91',
'0xb07079bd6238fa845dc77bbce3ec2edf98ffe735'
];
*/
// Each time 'keyContainer.createKeys()' is called, a new set of keys for an identity is created
// Retrieve key seed and its current derivation path
const { keySeed, pathKey } = keyContainer.getKeySeed();
// It should be noted that 'keys' are in form of ethereum addresses except
// key[1] that is a pubic key in its compressed form
const keyAddressOp = keys[0];
const keyPublicOp = keys[1];
const keyRecover = keys[2];
const keyRevoke = keys[3];
const db = new iden3.Db();
const keyContainer = new iden3.KeyContainer('localStorage', db);
const passphrase = 'pass';
keyContainer.unlock(passphrase);
// new relay
const relay = new iden3.Relay('http://127.0.0.1:8000/api/unstable');
const relayAddr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c';
const relay = new iden3.Relay(relayUrl);
// create identity object with a set of keys
const keyPath = 0;
const id = new iden3.Id(keyPublicOp, keyRecover, keyRevoke, relay, relayAddr, '', undefined, keyPath);
Creates the counterfactual contract through the Relay
, and gets the
identity address When an identity is created, all its keys are
automatically stored
id.createID().then(res => {
console.log(res.idAddr);
console.log(res.proofClaim);
});
// Return : - idAddr: Address identity identifier
// - proofOfClam: Structure of the claim emitted by the relay authorizing its key public operational
idAddr = 0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22;
proofClaim = {
date: 1549531663,
leaf:'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff50000000000000000000000000000000000007833000000000000000000000004',
proofs: [{
aux: {
era: 0,
idAddr: '0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22',
version: 0
}
mtp0: '0000000000000000000000000000000000000000000000000000000000000000',
mtp1: '030000000000000000000000000000000000000000000000000000000000000028f8267fb21e8ce0cdd9888a6e532764eb8d52dd6c1e354157c78b7ea281ce801541a6b5aa9bf7d9be3d5cb0bcc7cacbca26242016a0feebfc19c90f2224baed',
root: '1d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c3922',
} , {
aux: null
mtp0: '0000000000000000000000000000000000000000000000000000000000000000',
mtp1: '0300000000000000000000000000000000000000000000000000000000000000182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208a',
root: '083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f8199',
}],
signature:'440ec709297ecb6a7f7a200719c29d96025a893aef7318cebdcec401e3c8b3b711358f5a3c14394dc120b067ade86d7eca0c79be580d35934cc36dc246be6ec000',
}
// Create new key for this identity and bind it to a label
const labelKey = 'test key'
const loginKey = id.createKey(keyContainer, labelKey);
console.log(loginKey);
// Return : New key created
loginKey = '0xaac4ed37a11e6a9170cb19a6e558913dc3efa6a7';
// Retrieve all keys that have been created for this identity
const keysIdentity = id.getKeys();
console.log(keysIdentity);
// Return : Object containing all the keys associated with the identity
{
operationalPub:"0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833",
recover:"0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91",
revoke:"0xb07079bd6238fa845dc77bbce3ec2edf98ffe735",
test key:"0xaac4ed37a11e6a9170cb19a6e558913dc3efa6a7",
}
Deploys the counterfactual smart contract of identity to the blockchain.
id.deployID().then(res => {
console.log(res.data);
});
// Return object: - idAddr: Address identity identifier
// - tx: transaction identifier of the deploying identity smart contract on the blockchain
Vinculates a label to an identity. It sends required data to name-resolver service and name-resolver issue a claim ‘assignName’ binding identity address with a label
const name = 'testName';
id.bindID(kc, name).then(bindRes => {
console.log(bindRes.data);
});
- Output:
// Return object: - claimAssigName: hexadecimal representation of claim data
// - idAddr: ethereum addres to bind to the label
// - name: label binded to the ethereum address
// - proofClaimAssignName: full proof of existance of the claim issued by the name-resolved
{
claimAssigName: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f3715507449f3a8e1f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000000003',
idAddr: '0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22',
name: 'testName',
proofClaimAssignName: {
date:1549532610,
leaf:'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f3715507449f3a8e1f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000000003',
proofs:[{
aux: null,
mtp0:'0001000000000000000000000000000000000000000000000000000000000001083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f8199',
mtp1:'03010000000000000000000000000000000000000000000000000000000000010fef40cc16896de64be5a0f827799555344fd3d9aade9b65d95ecfbcac3e5a73182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208a',
root:'1b6feefde6e76c1e9d98d30fa0993a7a7b35f5b2580a757c9a57ee383dc50b96',
}],
signature:'1e6d15ef907000937577aa06437ee2a1230713be20ff09d7628ce4dc6c902c11274f34d4ae0f9e9fc2e67cf21abe5da7f11748fc243f4013faa42e53e9c81e3e01',
}
}
// generate new key from identity and add issue a claim to relay in order to authorize new key
const keyLabel = 'testKey';
const newKey = id.createKey(keyContainer, keyLabel, true);
// send claim to relay signed by operational key in order to authorize a second key 'newKey'
id.authorizeKSignSecp256k1(keyContainer, id.keyOperationalPub, loginKey)
.then((res) => {
console.error(res.data);
});
- Output:
// Return object: - proofClaim: full proof of existence of the claim issued by the relay
proofClaim = {
date: 1549534168,
leaf:'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aac4ed37a11e6a9170cb19a6e558913dc3ef000000000000000000000000000000000000a6a7000000000000000000000004',
proofs: [{
aux: {
era: 0,
idAddr: '0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22',
version: 1
}
mtp0: '00010000000000000000000000000000000000000000000000000000000000011d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c3922',
mtp1: '03010000000000000000000000000000000000000000000000000000000000011d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c39221c8bdcd862752abf2dd32d16c9c3acfa20ea93cecc64d169c4550ca3e9bca20b1541a6b5aa9bf7d9be3d5cb0bcc7cacbca26242016a0feebfc19c90f2224baed',
root: '21c6e1a81851f4017139ae8ddfbd5e894376fdd14c73cecf2a81939bae78595b',
} , {
aux: null
mtp0: '0007000000000000000000000000000000000000000000000000000000000041083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f81990fef40cc16896de64be5a0f827799555344fd3d9aade9b65d95ecfbcac3e5a73',
Mtp1: '0301000000000000000000000000000000000000000000000000000000000001081b6542453a651f2b0fea8b639a8823809f7fc032c051a644d1a8b559ba0322182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208a',
root: '1560e7b6983491305c6522c4227b98fbf26753b6a7fcb97ffb0ef7d98b271e99',
}],
signature:'3cedbb3d6eab5ce9a1f8bb436a080f7ec5ede3526fdcfa094fee33cbbd414d0c6d41a6650f4fdda27a66d51d87d18b4cae0adbd695ccdb152dae65a998ba61f101',
}
- Generic claim representation:
Entry
- Claim Types:
- Basic
- Authorize Key to sign
- Set root key
- Assign name
- Authorize key to sign secp256k1
/**
* Generic representation of claim elements
* Entry element structure is as follows: |element 0|element 1|element 2|element 3|
* Each element contains 253 useful bits enclosed on a 256 bits Buffer
*/
let entry = new iden3.Claim.Entry();
- Entry Methods:
entry.hi(); // Hash index is calculated from: |element 1|element 0|
entry.hv(); // Hash value is calculated from: |element 3|element 2|
entry.toHexadecimal(); // Concats all the elements of the entry and parse it into an hexadecimal string
entry.fromHexadecimal(); // String deserialization into entry element structure
const versionExample = 1;
const indexExample = Buffer.alloc(50);
indexExample.fill(41, 0, 1);
indexExample.fill(42, 1, 49);
indexExample.fill(43, 49, 50);
const dataExample = Buffer.alloc(62);
dataExample.fill(86, 0, 1);
dataExample.fill(88, 1, 61);
dataExample.fill(89, 61, 62);
// new basic claim
const claimBasic = new iden3.Claim.Factory(iden3.constants.CLAIMS.BASIC.ID, {
version: versionExample, index: utils.bytesToHex(indexExample), extraData: utils.bytesToHex(dataExample),
});
/*
claim.structure:
{
claimType,
version,
index,
extraData,
};
* Basic entry representation is as follows:
* |element 3|: |empty|index[0]|version|claim type| - |1 byte|19 bytes|4 bytes|8 bytes|
* |element 2|: |empty|index[1]| - |1 bytes|31 bytes|
* |element 1|: |empty|data[0]| - |1 bytes|31 bytes|
* |element 0|: |empty|data[1]| - |1 bytes|31 bytes|
*/
// methods of the Basic claim
claimBasic.createEntry(); // Code raw data claim object into an entry claim object
// parse Entry into Basic claim
let entry = new Entry();
entry.fromHexadecimal(leaf); // Leaf is an hexadecimal representation of an Entry
let claimBasicParsed = iden3.claim.claimUtils.newClaimFromEntry(entry);
const versionExample = 1;
const signExample = true;
const ayExample = '0x0505050505050505050505050505050505050505050505050505050505050506';
// new authorize ksign claim
const claimAuthorizeKSign = new Claim.Factory(iden3.constants.CLAIMS.AUTHORIZE_KSIGN.ID, {
version: versionExample, sign: signExample, ay: ayExample,
});
/*
claim.structure:
{
claimType,
version,
sign,
ay,
};
* Authorized Ksign element representation is as follows:
* |element 3|: |empty|sign|version|claim type| - |19 bytes|1 bytes|4 bytes|8 bytes|
* |element 2|: |Ay| - |32 bytes|
* |element 1|: |empty| - |32 bytes|
* |element 0|: |empty| - |32 bytes|
*/
// methods of the authorize Sign claim
claimAuthorizeKSign.createEntry(); // Code raw data claim object into an entry claim object
// parse Entry into authorize kSign claim
let entry = new Entry();
entry.fromHexadecimal(leaf); // Leaf is an hexadecimal representation of an Entry
let claimBasicParsed = iden3.claim.claimUtils.newClaimFromEntry(entry);
const versionExample = 1;
const eraExample = 1;
const idExample = '0x393939393939393939393939393939393939393A';
const rootKeyExample = '0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0c';
// new set root key ksign claim
const claimSetRootKey = new Claim.Factory(iden3.constants.CLAIMS.SET_ROOT_KEY.ID, {
version: versionExample, era: eraExample, id: idExample, rootKey: rootKeyExample,
});
/*
claim.structure:
{
claimType,
version,
er,
id,
rootKey,
};
* Set root key name entry representation is as follows:
* |element 3|: |empty|era|version|claim type| - |16 bytes|4 bytes|4 bytes|8 bytes|
* |element 2|: |empty|identity| - |12 bytes|20 bytes|
* |element 1|: |root key| - |32 bytes|
* |element 0|: |empty| - |32 bytes|
*/
// methods of the set root key claim
claimSetRootKey.createEntry(); // Code raw data claim object into an entry claim object
// parse Entry into set root key claim
let entry = new Entry();
entry.fromHexadecimal(leaf); // Leaf is an hexadecimal representation of an Entry
let claimBasicParsed = iden3.claim.claimUtils.newClaimFromEntry(entry);
const versionExample = 1;
const nameExample = 'example.iden3.eth';
const idExample = '0x393939393939393939393939393939393939393A';
// new set root key ksign claim
const claimAssignName = new Claim.Factory(CONSTANTS.CLAIMS.ASSIGN_NAME.ID, {
version: versionExample, hashName: nameExample, id: idExample
});
/*
claim.structure:
{
claimType,
version,
hashName,
id,
};
* Assign name entry representation is as follows:
* |element 3|: |empty|version|claim type| - |20 bytes|4 bytes|8 bytes|
* |element 2|: |hash name| - |32 bytes|
* |element 1|: |empty|identity| - |12 bytes|20 bytes|
* |element 0|: |empty| - |32 bytes|
*/
// methods of the set root key claim
claimAssignName.createEntry(); // Code raw data claim object into an entry claim object
// parse Entry into set root key claim
let entry = new Entry();
entry.fromHexadecimal(leaf); // Leaf is an hexadecimal representation of an Entry
let claimBasicParsed = iden3.claim.claimUtils.newClaimFromEntry(entry);
const versionExample = 1;
const pubKeyCompressedExample = '0x036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd4d593A';
// new authorize kSign secp256k1 claim
const claimAuthKSignSecp256k1 = new Claim.Factory(CONSTANTS.CLAIMS.AUTHORIZE_KSIGN_SECP256K1.ID, {
version: versionExample, pubKeyCompressed: utils.bytesToHex(pubKeyCompressedExample),
});
/*
claim.structure:
{
claimType,
version,
pubKeyCompressed,
};
* Authorized KsignSecp256k1 element representation is as follows:
* |element 3|: |empty|public key[0]|version|claim type| - |18 bytes|2 bytes|4 bytes|8 bytes|
* |element 2|: |empty|public key[1]| - |1 bytes|31 bytes|
* |element 1|: |empty| - |32 bytes|
* |element 0|: |empty| - |32 bytes|
*/
// methods of the authorize ksign secp256k1
claimAuthKSignSecp256k1.createEntry(); // Code raw data claim object into an entry claim object
// parse Entry into set root key claim
let entry = new Entry();
entry.fromHexadecimal(leaf); // Leaf is an hexadecimal representation of an Entry
let claimBasicParsed = iden3.claim.claimUtils.newClaimFromEntry(entry);
This function checks the data structure of proofOfClaim
and returns
true if all the proofs are correct. Internally, it usees the
iden3.sparseMerkleTree.checkProof()
function, for each one of the
proofs that are contained inside proofClaim
data object.
Checks the full proof
of a claim
. This means check the: -
Merkle Proof
of the claim
- Merkle Proof
of the non
revocation claim
- Merkle Proof
of the claim
that the
Relay
have performed over the identity
Merkle Root
(this
kind of claim is the SetRootClaim
) - Merkle Proof
of the non
revocation of the SetRootClaim
let proofClaim = {
date: 1549534168,
leaf:'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aac4ed37a11e6a9170cb19a6e558913dc3ef000000000000000000000000000000000000a6a7000000000000000000000004',
proofs: [{
aux: {
era: 0,
idAddr: '0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22',
version: 1
}
mtp0: '00010000000000000000000000000000000000000000000000000000000000011d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c3922',
mtp1: '03010000000000000000000000000000000000000000000000000000000000011d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c39221c8bdcd862752abf2dd32d16c9c3acfa20ea93cecc64d169c4550ca3e9bca20b1541a6b5aa9bf7d9be3d5cb0bcc7cacbca26242016a0feebfc19c90f2224baed',
root: '21c6e1a81851f4017139ae8ddfbd5e894376fdd14c73cecf2a81939bae78595b',
} , {
aux: null
mtp0: '0007000000000000000000000000000000000000000000000000000000000041083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f81990fef40cc16896de64be5a0f827799555344fd3d9aade9b65d95ecfbcac3e5a73',
mtp1: '0301000000000000000000000000000000000000000000000000000000000001081b6542453a651f2b0fea8b639a8823809f7fc032c051a644d1a8b559ba0322182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208a',
root: '1560e7b6983491305c6522c4227b98fbf26753b6a7fcb97ffb0ef7d98b271e99',
}],
signature:'3cedbb3d6eab5ce9a1f8bb436a080f7ec5ede3526fdcfa094fee33cbbd414d0c6d41a6650f4fdda27a66d51d87d18b4cae0adbd695ccdb152dae65a998ba61f101',
}
let proofClaim = JSON.parse(proofClaim);
let verified = iden3.protocols.verifyProofClaimFull(proofClaim, relayAddr);
// verified === true
Three parameters as an inputs: - db –> where to store key-value merkle tree nodes - idaddr –> used as key prefix at the time to store key nodes
// New database
const db = new iden3.Db();
// Hardcoded id address for multi identity purposes
const idAddr = '0xq5soghj264eax651ghq1651485ccaxas98461251d5f1sdf6c51c5d1c6sd1c651';
// New merkle tree class instance
const mt = new iden3.sparseMerkleTree.SparseMerkleTree(db, idAddr);
Add a leaf into the sparse merkle tree. Note the leaf object structure
containing 4 bigInt
fields
// Add leaf
// Create data leaf structure
const leaf = [bigInt(12), bigInt(45), bigInt(78), bigInt(41)];
// Add leaf to the merkle tree
mt.addClaim(leaf);
Look for a index leaf on the merkle tree ans retrieves its data
// Get leaf data by hash Index
// Retrieve data of the leaf
const leafData = mt.getClaimByHi(leaf.slice(2));
Generates an array with all the siblings needed in order to proof that a certain leaf is on a merkle tree.
// Get leafProof for a given leaf index
const leafProof = mt.generateProof(leaf.slice(2));
// Code `leafProof` into a hexadecimal string
const leafProofHex = iden3.utils.bytesToHex(leafProof);
Checks the Merkle Proof
of a Leaf
. ##### Proof-of-existence
// CheckProof
// Proof-of-existencee
// Retrieve merkle tree root and code it into a string
const rootHex = iden3.utils.bytesToHex(mt.root);
// Code hash index into a hexadecimal string
// Compute total hash of the leaf and code it into an hexadecimal string
const hashes = iden3.sparseMerkleTree.getHiHv(leaf);
const hiHex = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes[0]));
const hvHex = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes[1]));
// Check if a leaf is on the merkle tree
const verified = iden3.sparseMerkleTree.checkProof(rootHex, leafProofHex, hiHex, hvHex);
Generates leafProof
of a leaf that is not on the merkle tree and
check if it is on the merkle tree.
// CheckProof
// Proof-of-non-existence
// create leaf2 data structure
const leaf2 = [bigInt(1), bigInt(2), bigInt(3), bigInt(4)];
// Code hash index into a hexadecimal string
// Compute total hash of the leaf and code it into an hexadecimal string
const hashes2 = iden3.sparseMerkleTree.getHiHv(leaf2);
const hiHex2 = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes2[0]));
const hvHex2 = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes2[1]));
// Get leafProof for a given leaf index
const leafProof2 = mt.generateProof(leaf2.slice(2));
// Code `leafProof` into a hexadecimal string
const leafProofHex2 = iden3.utils.bytesToHex(leafProof2);
// Check if a leaf is on the merkle tree
const verified2 = iden3.sparseMerkleTree.checkProof(rootHex, leafProofHex2, hiHex2, hvHex2);
The complete example can be found in
`sparse-merkle-tree.example.js
<https://github.com/iden3/iden3js/blob/master/examples/sparse-merkle-tree.example.js>`__
// hash Buffer
let hash = iden3.utils.hashBytes(b);
let hex = iden3.utils.bytesToHex(buff); // returns a Hexadecimal representation of a Buffer
let buff = iden3.utils.hexToBytes(hex); // returns a Buffer from a Heximal representation string
// verify signature
let verified = iden3.utils.verifySignature(msgHashHex, signatureHex, addressHex);
// verified: true
Connectors to interact with the relay API REST.
// new relay
const relayAddr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c';
const relay = new iden3.Relay('http://127.0.0.1:8000/api/unstable');
relay.getID(id.idAddr).then((res) => {
console.log(res.data);
});
- Output:
// Return object: - IdAddr: Address identity identifier
// - LocalDb: contins necessary informatin to create counterfactoual
// - Onchain: information regarding smart contract deployed on the blockchain
{
IdAddr: '0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22',
LocalDb: {
impl:'0x66d0c2f85f1b717168cbb508afd1c46e07227130',
operational:'0xc7d89fe96acdb257b434bf580b8e6eb677d445a9',
operationalPk:'0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833',
recoverer:'0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91',
relayer:'0xe0fbce58cfaa72812103f003adce3f284fe5fc7c',
revokator:'0xb07079bd6238fa845dc77bbce3ec2edf98ffe735',
},
onchain: {
Codehash:'0x4fec321ffcfdd48cdbe4d02553acb18ddb04cd5c6a78bcaf86e87834b1f3d0ee',
Impl:'0x66d0c2f85f1b717168cbb508afd1c46e07227130',
LastNonce:0,
Recoverer:'0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91',
RecovererProp:'0x0000000000000000000000000000000000000000',
Relay:'0xe0fbce58cfaa72812103f003adce3f284fe5fc7c',
Revoker:'0xb07079bd6238fa845dc77bbce3ec2edf98ffe735',
},
}
relay.getRelayRoot()
.then(res => {
console.log('res.data', res.data);
});
- Output:
// Return object: - contractRoot: Address of the relay smart contract
// - root: Current root of the relay merkle tree
{
contractRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
root: '0x1560e7b6983491305c6522c4227b98fbf26753b6a7fcb97ffb0ef7d98b271e99'
}
relay.getIDRoot(id.kc.addressHex())
.then(res => {
console.log('res.data', res.data);
});
- Output:
// Return object: - idRoot: Root of the identity merkle tree
// - proofIdRoot: Proof of SetRootClaim that relay merkle tree contains identity root merkle tree
// - root: Root of the relay merkle tree
{
idRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
proofIdRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
root: '0x0000000000000000000000000000000000000000000000000000000000000000'
}
let leaf = new iden3.claims.Entry();
leaf.fromHexadecimal(proofClaim.Leaf);
relay.getClaimByHi(id.idAddr, iden.utils.bytesToHex(leaf.hi()))
.then(res => {
console.log('res.data', res.data);
});
// Return object: - proofOfClaim: Proof of claim for the claim asked
proofClaim = {
date: 1549534168,
leaf:'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aac4ed37a11e6a9170cb19a6e558913dc3ef000000000000000000000000000000000000a6a7000000000000000000000004',
proofs: [{
aux: {
era: 0,
idAddr: '0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22',
version: 1
}
mtp0: '00010000000000000000000000000000000000000000000000000000000000011d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c3922',
mtp1: '03010000000000000000000000000000000000000000000000000000000000011d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c39221c8bdcd862752abf2dd32d16c9c3acfa20ea93cecc64d169c4550ca3e9bca20b1541a6b5aa9bf7d9be3d5cb0bcc7cacbca26242016a0feebfc19c90f2224baed',
root: '21c6e1a81851f4017139ae8ddfbd5e894376fdd14c73cecf2a81939bae78595b',
} , {
aux: null
mtp0: '0007000000000000000000000000000000000000000000000000000000000041083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f81990fef40cc16896de64be5a0f827799555344fd3d9aade9b65d95ecfbcac3e5a73',
mtp1: '0301000000000000000000000000000000000000000000000000000000000001081b6542453a651f2b0fea8b639a8823809f7fc032c051a644d1a8b559ba0322182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208a',
root: '1560e7b6983491305c6522c4227b98fbf26753b6a7fcb97ffb0ef7d98b271e99',
}],
signature:'3cedbb3d6eab5ce9a1f8bb436a080f7ec5ede3526fdcfa094fee33cbbd414d0c6d41a6650f4fdda27a66d51d87d18b4cae0adbd695ccdb152dae65a998ba61f101',
}
relay.resolveName('username@iden3.io')
.then(res => {
console.log('res.data', res.data);
});
- Output:
// Return object: - claim: Hexadecimal representation of the assign name claim
// - idAddr: Ethereum address associated with the name asked
// - proofOfClaimAssignName: Proof of the claim requested
{
claim: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f3715507449f3a8e1f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000000003',
ethAddr: '0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22'.
proofOfClaimAssignName: {
date: 1549539788,
leaf: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f3715507449f3a8e1f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000000003',
proofs:[{
aux: null,
mtp0: '0007000000000000000000000000000000000000000000000000000000000041083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f8199200d11c36880f3f48060bc8f09855aeefc9bb1e1374556d02c3f059293df4abe',
mtp1: '0301000000000000000000000000000000000000000000000000000000000001081b6542453a651f2b0fea8b639a8823809f7fc032c051a644d1a8b559ba0322182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208a',
root: '1560e7b6983491305c6522c4227b98fbf26753b6a7fcb97ffb0ef7d98b271e99',
}]
signature:'0b17f53111f890222d8139e0a400f9dbf900dabdc450759ac9ab19fb9f239f704d250cd3116b6f74905ffccd8754182d3de2e1fc4ac7a35b0db6fe660198422000',
},
}
Tests¶
To run unitary test:
npm run test:unit
To run integration test, needs to have a running Relay node.
npm run test:int
To run all test, needs to have a running Relay node.
npm run test:all
Browserify bundle¶
To generate the browserify bundle:
npm run browserify
All code here is experimental and WIP
Go-Iden3¶
Overview¶
go-iden3¶
Go implementation of the iden3 system.
Usage¶
- Relay Documentation: https://github.com/iden3/go-iden3/blob/master/Relay.md
- MerkleTree Documentation: https://github.com/iden3/go-iden3/blob/master/merkletreeDoc/merkletree.md
- Backup Server Documentation: https://github.com/iden3/go-iden3/blob/master/cmd/backupserver/README.md
Documentation¶
Go Modules documentation: - GoDoc common - GoDoc core - GoDoc db - GoDoc eth - GoDoc crypto - GoDoc merkletree - GoDoc utils - GoDoc services/backupsrv - GoDoc services/centrauthsrv - GoDoc services/claimsrv - GoDoc services/identitysrv - GoDoc services/mongosrv - GoDoc services/namesrv - GoDoc services/rootsrv - GoDoc services/signsrv
Merkle Tree¶
Contents
Merkletree usage¶
Import¶
Import packages:
import (
"github.com/iden3/go-iden3/db"
"github.com/iden3/go-iden3/merkletree"
"github.com/iden3/go-iden3/core"
common3 "github.com/iden3/go-iden3/common"
)
New Merkletree¶
Define new tree:
// first we create the storage, where will be placed the leveldb database
storage, err := db.NewLevelDbStorage("./path", false)
if err!=nil {
panic(err)
}
// new merkletree of 140 levels of maximum depth using the defined
// storage
mt, err := merkletree.NewMerkleTree(storage, 140)
if err!=nil {
panic(err)
}
defer mt.Storage().Close()
Add claims¶
To add claims, first we need to have a claim data struct that fits the
Entrier
interface:
// Data consists of 4 elements of the mimc7 field.
type Data [4]ElemBytes
// An Entry contains Data where the claim will be serialized.
type Entry struct {
Data Data
[...]
}
// Entrier is the interface of a generic claim.
type Entrier interface {
Entry() *Entry
}
We can use a new struct, or also use one of the already existing in the
go-iden3/core/claim.go
.
For this example, we will use the core.ClaimAssignName
. We add two
different claims into the merkletree:
name0 := "alice@iden3.io"
ethAddr0 := common.HexToAddress("0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22")
claim0 := core.NewClaimAssignName(name0, ethAddr0)
claimEntry0 := claim0.Entry()
name1 := "bob@iden3.io"
ethAddr1 := common.HexToAddress("0x28f8267fb21e8ce0cdd9888a6e532764eb8d52dd")
claim1 := core.NewClaimAssignName(name1, ethAddr1)
claimEntry1 := claim1.Entry()
Once we have the claim
struct that fits the Entrier
interface,
we can add it to the merkletree:
err = mt.Add(claimEntry0)
if err != nil {
panic(err)
}
err = mt.Add(claimEntry1)
if err != nil {
panic(err)
}
Generate merkle proof¶
Now we can generat the merkle proof of this claim:
mp, err := mt.GenerateProof(claimEntry0.HIndex(), nil)
if err != nil {
panic(err)
}
// We can display the merkleproof:
fmt.Println("merkle proof: ", mp)
// out:
// merkle proof: Proof:
// existence: true
// depth: 2
// notempties: 01
// siblings: 0 a045683a
mp, err := mt.GenerateProof(claimEntry0.HIndex(), specificRoot)
if err != nil {
panic(err)
}
Check merkle proof¶
Now from a given merkle proof, we can check that it’s data is consistent:
checked := merkletree.VerifyProof(mt.RootKey(), mp,
claimEntry0.HIndex(), claimEntry0.HValue())
// checked == true
Get value in position¶
We can also get the claim
byte data in a certain position of the
merkle tree (determined by its Hash Index (HIndex
)):
claimDataInPos, err := mt.GetDataByIndex(claimEntry0.HIndex())
if err!=nil{
panic(err)
}
Proof of non existence¶
Also, we can generate a Proof of non existence
, that is, the merkle
proof that a claim is not in the tree. For example, we have this
claim2
that is not added in the merkletree:
name2 := "eve@iden3.io"
ethAddr2 := common.HexToAddress("0x29a6a240e2d8f8bf39b5338b9664d414c5d793f4")
claim2 := core.NewClaimAssignName(name2, ethAddr2)
claimEntry2 := claim2.Entry()
Now, we can generate the merkle proof of the data in the position of this claim in the merkletree, and print it to see that it’s a non-existence proof:
mp, err = mt.GenerateProof(claimEntry2.HIndex(), nil)
if err != nil {
panic(err)
}
// We can display the merkleproof:
fmt.Println("merkle proof: ", mp)
// out:
// merkle proof: Proof:
// existence: false
// depth: 2
// notempties: 01
// siblings: 0 a045683a
// node aux: hi: c641b925, ht: eeae8c7e
In the mp
we have the merkleproof that in the position of this
claim2
(that is determined by its Hash Index (HIndex
)) there is
no data stored (so, it’s an NodeTypeEmpty
not actually stored in the
tree).
We can check this proof by calling the VerifyProof
function, and in
the parameter where we put the Hash Total (HtTotal
) we can actually
put anything, because we can proof that anything is not there. We will
use the Hash Total of the claim2 for convenience.
checked = merkletree.VerifyProof(mt.RootKey(), mp, claimEntry2.HIndex(), claimEntry2.HValue())
// checked == true
Live snapshot of the tree¶
This option allows to create a read only snapshot
of the Merkle
Tree, where to perform read actions, while the main tree continues
evolving. Basically what it does is to create a new MerkleTree
object with the specified Root
. Also this Merkle Tree snapshot will
not allow to add nodes into it.
snapshot, err := mt.Snapshot(concreteRootKey)
if err!=nil {
panic(err)
}
// now we can for example, generate proofs for that snapshot of the Merkle Tree
mp, err := snapshot.GenerateProof(claimEntry0.HIndex())
if err != nil {
panic(err)
}
// and the mp (merkleproof) will be valid for the root of the snapshot
Walk over the Merkle Tree¶
Walk option allows to iterate through all the branches of a tree with a
given RootKey
. It allows to give a funcion that will be called
inside each node of the tree, returning the a pointer to that Node
object.
When calling the Walk
function, we can specify a RootKey
We can use a BufferString, that we can use to print to the screen or
also into a file all the Leaf
nodes:
w := bytes.NewBufferString("")
// mt.Walk(nil, [...] --> as we specify the RootKey as nil, it will use the current mt.RootKey()
err := mt.Walk(nil, func(n *Node) {
if n.Type == NodeTypeLeaf {
fmt.Fprintf(w, "node \"%v\"\n", common3.HexEncode(n.Value()))
}
})
if err != nil {
panic(err)
}
fmt.Println(w)
Or also we can just print each node inside a switch (also, in this case,
we specify a concrete RootKey
of the MerkleTree
that we want to
use):
err := mt.Walk(concreteRootKey, func(n *Node) {
switch n.Type {
case NodeTypeEmpty:
fmt.Println("empty")
case NodeTypeLeaf:
fmt.Println("leaf \"%v\"\n", common3.HexEncode(n.Value()))
case NodeTypeMiddle:
fmt.Println("node \"%v\"\n", common3.HexEncode(n.Value()))
default:
return ErrInvalidNodeFound
}
})
if err != nil {
panic(err)
}
Dump all the claims of a MerkleTree¶
Using this function we can dump all the claims of a MerkleTree, allowing
us also to specify a concrete RootKey
of the tree that we want to
dump. The output generated by this method, can be used from the
iden3js library, importing all
the dumped claims, allowing to have the same tree from go in javascript.
w := bytes.NewBufferString("")
err := mt.DumpClaims(w, rootKey) // as rootKey we can pass a nil pointer, and it will use the current RootKey
if err!=nil {
panic(err)
}
fmt.Println(w)
Merkle tree visual representation¶
Finally, you can get a visual representation of the merkle tree with graphviz, in which only the last bytes of each node key are shown in hexadecimal to make a compact representation. In the following example print the graphviz code for the representation of a merkle tree with 10 claims:
s := bytes.NewBufferString("")
mt2.GraphViz(s, nil)
fmt.Println(s)
GraphViz output code:
digraph hierarchy {
node [fontname=Monospace,fontsize=10,shape=box]
"b0830ca8" -> {"5ae3cb67" "570088ed"}
"5ae3cb67" -> {"6ce2c761" "37b29928"}
"empty0" [style=dashed,label=0];
"6ce2c761" -> {"e82bf1e7" "empty0"}
"e82bf1e7" -> {"6ee500bf" "63289a99"}
"6ee500bf" [style=filled];
"63289a99" [style=filled];
"37b29928" -> {"ef87b970" "8b6e9f1c"}
"ef87b970" -> {"1481190a" "93c79331"}
"1481190a" [style=filled];
"93c79331" [style=filled];
"8b6e9f1c" [style=filled];
"570088ed" -> {"4f3d0101" "8a2524f6"}
"4f3d0101" -> {"74924bbf" "6aa34a3d"}
"74924bbf" [style=filled];
"empty1" [style=dashed,label=0];
"6aa34a3d" -> {"empty1" "69003eca"}
"69003eca" -> {"fac8618d" "43f442c5"}
"fac8618d" [style=filled];
"43f442c5" [style=filled];
"8a2524f6" -> {"3f5e7a5f" "d76c6447"}
"3f5e7a5f" [style=filled];
"d76c6447" [style=filled];
}
The GraphViz visualization looks like this:
You can also view the graph using an online graphviz renderer.
Complete example¶
The complete example can be found in this directory in the file
`example.go
<https://github.com/iden3/go-iden3/blob/master/merkletreeDoc/example.go>`__.
Backup Server¶
Contents
Backup Server¶
Backup Service¶
Wallet Backup Service
+ +
| /register |
+------------------------>+
| 200 OK |
+<------------------------+
| |
| |
| /backup/upload |
+------------------------>+
| 200 OK |
+<------------------------+
| |
| |
| /backup/download |
+------------------------>+
| {backup} |
+<------------------------+
| |
+ +
POST /register
- in:
{ username: "", password: "" }
- out:
200 OK
POST /backup/upload
- in:
{ username: "", password: "" backup: "base64" }
- out:
200 OK
POST /backup/download
- in:
{ username: "", password: "" }
- out:
{ backup: "base64" }
ERROR
- out:
{ error: "msg" }
Relay¶
Contents
Relay¶
Usage¶
To run a relay we need to build the contracts used for iden3:
git clone https://github.com/iden3/contracts
cd contracts
npm install
node_modules/.bin/truffle compile
Then we create a keystore protected by a password. First you need to install geth. Then run the following commands with your password (we use THEPASSWORD here as an example):
echo "THEPASSWORD" > config-path/keystore.password
geth account new --keystore config-path/keystore # Input the same password as the one stored in keystore.password
Then we will need to have a config file config.yaml
with the
following data:
server:
serviceapi: 127.0.0.1:8000
adminapi: 127.0.0.1:8001
web3:
url: http://web3-gateway-url
keystore:
path: config-path/keystore
address: 0xe0fbce58cfaa72812103f003adce3f284fe5fc7c
password: config-path/keystore.password
contracts:
rootcommits:
jsonabi: contracts-repo-path/build/contracts/RootCommits.json
address: 0x6A6E04938d66Df5717ec4774E0ca181077e842ed
iden3impl:
jsonabi: contracts-repo-path/build/contracts/IDen3Impl.json
address: 0x66D0c2F85F1B717168cbB508AfD1c46e07227130
iden3deployer:
jsonabi: contracts-repo-path/build/contracts/Deployer.json
address: 0xf02e236F9F6C08966DD63B9fB9C04764E01b0563
iden3proxy:
jsonabi: contracts-repo-path/build/contracts/IDen3DelegateProxy.json
storage:
path: /tmp/treedb
domain: iden3.io
namespace: iden3.io
In the go-iden3/cmd/relay
directory, run:
go run main.go --config path-to-config/config.yaml start
Also can be built using:
go build
And then execute:
./relay
API Endpoints¶
Returns:
{
"contractRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"root": "0x0458f3531f8292918aabef2d5f1c5a0c35da251c66c3f9d33eb4077e9ed0ec36"
}
Input:
{
"valueHex": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000025521b25f396b1f62fcc46ce5b9a6b53684d5649958d83d79b5bb6711aa270000000000000000000000000000000000009105000000000000000000000004"
,
"signatureHex": "0xd7cfe7c0935e27a6ce3c587da2f55a5f6765b859f57baddd22a232bf12563ac60cd91f6c1046acfd2c3d148f9d082e0ec194d72f3f1b2ead7985
9809fa09bcae1c",
"ksignpk": "0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833"
}
Returns:
{
"proofClaim": {
"proofs": [
{
"mtp0": "0x00030000000000000000000000000000000000000000000000000000000000041d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8
ad761c3922",
"mtp1": "0x0303000000000000000000000000000000000000000000000000000000000004294c2853becf85699f4d65fa57bd43e5c2e7087e23945d2c5ec52f
903443139728f8267fb21e8ce0cdd9888a6e532764eb8d52dd6c1e354157c78b7ea281ce801541a6b5aa9bf7d9be3d5cb0bcc7cacbca26242016a0feebfc19c90f2224bae
d",
"root": "0x26815c474fa21c55dbef8e8628fc418946b147278f42402db7f07e4324ae9c5f",
"aux": {
"version": 1,
"era": 0,
"ethAddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22"
}
},
{
"mtp0": "0x0001000000000000000000000000000000000000000000000000000000000001083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59
fad48f8199",
"mtp1": "0x0301000000000000000000000000000000000000000000000000000000000001296c58506f1f3ecb09122a8eac285cd363840e2da8180d61188f0a
c78189b96a182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208
a",
"root": "0x141a1d2dceec7ff08497d15fc092f18ac460c8654ff9fed6626c1d66eeb3c75b",
"aux": null
}
],
"leaf": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000025521b25f396b1f62fcc46ce5b9a6b53684d5649958d83d79b5bb6711aa270000000000000000000000000000000000009105000000000000000000000004",
"date": 1548849932,
"signature": "0x224dca4c57fb4c4bb946ec1ba82cf46d2f12da5a1a73fe143bfcd3ae20212975519c9d711ce2c4e414eae950b28be741e6b9721cd663d71fb2a48
44efa5a84ed00"
}
}
Returns:
{
"idRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"idRootProof": "0x01020000000000000000000000000000000000000000000000000000000000030df6a62218b641b022bbd990303ec57411ebfe24965af84a7d3e4
dc8e92d46bb2c7df576dfac28d7b2a9a534e1d099e0438f04f66bebaa03a2349860d26e2e62",
"root": "0x0458f3531f8292918aabef2d5f1c5a0c35da251c66c3f9d33eb4077e9ed0ec36"
}
4eb8d52dd6c1e354157c78b7ea281ce80 Returns:
{
"proofClaim": {
"proofs": [
{
"mtp0": "0x00030000000000000000000000000000000000000000000000000000000000051b12c5489d45a9759a0aa761b4031fc4fa4afac3d6315273eecd13
58d562b9de294c2853becf85699f4d65fa57bd43e5c2e7087e23945d2c5ec52f9034431397",
"mtp1": "0x03030000000000000000000000000000000000000000000000000000000000051b12c5489d45a9759a0aa761b4031fc4fa4afac3d6315273eecd13
58d562b9de1d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c39221a81d39b8b3f86e7a4b3df400dcb541f478df56414d3bd0d4b3cfa2f8e7df07
c1541a6b5aa9bf7d9be3d5cb0bcc7cacbca26242016a0feebfc19c90f2224baed",
"root": "0x1a99534d2fad42577649c8fa0af4c2b5610f316f7bf29814ba36a2c4f1e76c21",
"aux": {
"version": 2,
"era": 0,
"ethAddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22"
}
},
{
"mtp0": "0x00020000000000000000000000000000000000000000000000000000000000031744e6cadba4793eacdfb8d32e955ea12f976b72cef88059e09bb5
f6ea5d9de0083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f8199",
"mtp1": "0x01020000000000000000000000000000000000000000000000000000000000030df6a62218b641b022bbd990303ec57411ebfe24965af84a7d3e4d
c8e92d46bb2c7df576dfac28d7b2a9a534e1d099e0438f04f66bebaa03a2349860d26e2e62",
"root": "0x0458f3531f8292918aabef2d5f1c5a0c35da251c66c3f9d33eb4077e9ed0ec36",
"aux": null
}
],
"leaf": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000003c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff50000000000000000000000000000000000007833000000000000000000000004",
"date": 1548849932,
"signature": "0xd18b60beb56a40dcb4ad5648d3b7d122137aa75f96c858cd1e8f0999cb02f35255897d9a6cbbf9df02745c36e6ac4ad2a7a839b81ae4941bcbd4c
0136cb76b5200"
}
}
Input:
{
"operationalpk": "0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833",
"recoverer": "0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91",
"revokator": "0xb07079bd6238fa845dc77bbce3ec2edf98ffe735"
}
Returns:
{
"idaddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22",
"proofClaim": {
"proofs": [
{
"mtp0": "0x0000000000000000000000000000000000000000000000000000000000000000",
"mtp1": "0x030000000000000000000000000000000000000000000000000000000000000028f8267fb21e8ce0cdd9888a6e532764eb8d52dd6c1e354157c78b
7ea281ce801541a6b5aa9bf7d9be3d5cb0bcc7cacbca26242016a0feebfc19c90f2224baed",
"root": "0x1d9d41171c4b621ff279e2acb84d8ab45612fef53e37225bdf67e8ad761c3922",
"aux": {
"version": 0,
"era": 0,
"ethAddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22"
}
},
{
"mtp0": "0x0000000000000000000000000000000000000000000000000000000000000000",
"mtp1": "0x0300000000000000000000000000000000000000000000000000000000000000182adc955c46e6629ac74027ded0c843c7c65e8c3c4f12f77add56
500f9f402e25451237d9133b0f5c1386b7b822f382cb14c5fff612a913956ef5436fb6208a",
"root": "0x083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f8199",
"aux": null
}
],
"leaf": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000003c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff50000000000000000000000000000000000007833000000000000000000000004",
"date": 1548849932,
"signature": "0x65312b0604555dd6a406e394d2174bae040a22c13143d3f97b282d55619315765e4fb4f783aa4c26979dc9bbe51ff6c17c1176f57c140a3120e3e
3d2f9044f1001"
}
}
Returns:
{
"IdAddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22",
"LocalDb": {
"Operational": "0xc7d89fe96acdb257b434bf580b8e6eb677d445a9",
"OperationalPk": "0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833",
"Relayer": "0xe0fbce58cfaa72812103f003adce3f284fe5fc7c",
"Recoverer": "0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91",
"Revokator": "0xb07079bd6238fa845dc77bbce3ec2edf98ffe735",
"Impl": "0x66d0c2f85f1b717168cbb508afd1c46e07227130"
},
"Onchain": {
"Codehash": "0x4fec321ffcfdd48cdbe4d02553acb18ddb04cd5c6a78bcaf86e87834b1f3d0ee",
"Impl": "0x66d0c2f85f1b717168cbb508afd1c46e07227130",
"Recoverer": "0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91",
"RecovererProp": "0x0000000000000000000000000000000000000000",
"Revoker": "0xb07079bd6238fa845dc77bbce3ec2edf98ffe735",
"Relay": "0xe0fbce58cfaa72812103f003adce3f284fe5fc7c",
"LastNonce": 0
}
}
Input:
{}
Returns:
{
idaddr: '0x8435ebb41634c05019be1710be0007fa0d92861f',
tx: '0x403859ccc701eb358d3a25c908c33de733cbb2d0ebc1c7738eed4908cc8cf5c4'
}
Input:
{
"ethAddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22",
"name": "testName",
"signature": "0x8526016fb5f0fda5d04b37725768a82f17c7886541445304730bcf021c96e5ce6181b3a6e1d1ca4faa68f802d169514664f576d006fe872e646c96a
e9a75d6c11c",
"ksignpk": "0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833"
}
Returns:
{
"claimAssignName": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f371550
7449f3a8e1f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000
000003",
"ethAddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22",
"name": "testName",
"proofClaimAssignName": {
"proofs": [
{
"mtp0": "0x00070000000000000000000000000000000000000000000000000000000000410df6a62218b641b022bbd990303ec57411ebfe24965af84a7d3e4d
c8e92d46bb296c58506f1f3ecb09122a8eac285cd363840e2da8180d61188f0ac78189b96a",
"mtp1": "0x03020000000000000000000000000000000000000000000000000000000000031744e6cadba4793eacdfb8d32e955ea12f976b72cef88059e09bb5
f6ea5d9de0083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f8199137896c5dde3243fba9080cd9eb1aad51a293091da7afd15b05f54a82a4633a
c10b8436ad110ba4812e91e282ef9ef833006cda841f9121345a2eb8f76ed09bd",
"root": "0x0458f3531f8292918aabef2d5f1c5a0c35da251c66c3f9d33eb4077e9ed0ec36",
"aux": null
}
],
"leaf": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f3715507449f3a8e
1f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000000003",
"date": 1548849932,
"signature": "0xd18b60beb56a40dcb4ad5648d3b7d122137aa75f96c858cd1e8f0999cb02f35255897d9a6cbbf9df02745c36e6ac4ad2a7a839b81ae4941bcbd4c
0136cb76b5200"
}
}
Returns:
{
"claim": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f3715507449f3a8e1
f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000000003",
"ethAddr": "0x7b471a1bdbd3b8ac98f3715507449f3a8e1f3b22",
"proofClaimAssignName": {
"proofs": [
{
"mtp0": "0x00070000000000000000000000000000000000000000000000000000000000410df6a62218b641b022bbd990303ec57411ebfe24965af84a7d3e4d
c8e92d46bb296c58506f1f3ecb09122a8eac285cd363840e2da8180d61188f0ac78189b96a",
"mtp1": "0x03020000000000000000000000000000000000000000000000000000000000031744e6cadba4793eacdfb8d32e955ea12f976b72cef88059e09bb5
f6ea5d9de0083dbb7700313075a2b8fe34b0188ff44784e3dc60987ed9277b59fad48f8199137896c5dde3243fba9080cd9eb1aad51a293091da7afd15b05f54a82a4633a
c10b8436ad110ba4812e91e282ef9ef833006cda841f9121345a2eb8f76ed09bd",
"root": "0x0458f3531f8292918aabef2d5f1c5a0c35da251c66c3f9d33eb4077e9ed0ec36",
"aux": null
}
],
"leaf": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b471a1bdbd3b8ac98f3715507449f3a8e
1f3b22008c8efcda9e563cf153563941b60fc5ac88336fc58d361eb0888686fadb99760000000000000000000000000000000000000000000000000000000000000003",
"date": 1548849932,
"signature": "0xd18b60beb56a40dcb4ad5648d3b7d122137aa75f96c858cd1e8f0999cb02f35255897d9a6cbbf9df02745c36e6ac4ad2a7a839b81ae4941bcbd4c
0136cb76b5200"
}
}
Crypto¶
Contents
crypto GoDoc¶
iden3 crypto Go package
MIMC7¶
MIMC7 Hash function
Usage:
package main
import (
"math/big"
"github.com/iden3/go-iden3/crypto/mimc7"
)
func mimc7Example() {
// for this example, define an array of big ints to hash
b1 := big.NewInt(int64(1))
b2 := big.NewInt(int64(2))
b3 := big.NewInt(int64(3))
bigArr := []*big.Int{b1, b2, b3}
arr, err := mimc7.BigIntsToRElems(bigArr)
// mimc7 hash
h := mimc7.Hash(arr)
fmt.Println((*big.Int)(h).String())
// h == 10001192134743444757278983923787274376044444355175924720153500128284360571540
}
Tx Forwarder¶
Overview¶
Contents
tx-forwarder¶
Server that forwards the tx to the specified smart contract.
Usage¶
Deploy contract:
./tx-forwarder deploy
This will print the deployed contract address, then copy&paste in the
config file config.yaml
:
server:
serviceapi: 0.0.0.0:11000
adminapi: 0.0.0.0:11001
web3:
url: http://127.0.0.1:8545
keystore:
path: /var/config/keystore
address: 0x123456789...
passwd: /var/config/keystore.password
keyjsonpath: /var/config/keystore/UTC-etc
contracts:
samplecontract: 0xasdf
Current contract is just a working sample contract.
The contract handlers goes inside eth/contract/
directory.
Once having the contract file written in Solidity, in order to generate the Go handlers:
solc --abi --bin SampleContract.sol -o build
abigen --bin=./build/SampleContract.bin --abi=./build/SampleContract.abi --pkg=SampleContract --out=SampleContract.go
And place the sampleContract.go
file in the
eth/contract/sampleContract.go
path.
Discovery Node¶
Overview¶
discovery-node Go Report Card GoDoc¶
Draft implementation of discovery-node
of the decentralized
discovery protocol over Pss Swarm
Overview¶

network00
Types of node: - gateway
(passive
): are the nodes that only
perform petitions, acting as gateways to the discovery network. This
node can be trustless, as all the data that gets from the network and
that is returned to its petitions, have the proofs of validity
(merkleproofs) - active
: are the nodes that are answering requests,
each identity trusts its active discovery-node
The discovery-node
data storage is a leveldb database. It’s
organized with prefixes, where each type of data is stored under a
prefix.
Databases: - dbOwnIds
: holds the data about the identities that the
discovery-node
manages - dbAnswCache
: holds the data about the
discovered identites. Each data packet of a discovered identity, has a
timestamp
, the data packets are valid under a time window where the
timestamp
allows to determine if it’s already valed or is too old
Roles: - user
: user identity, from a phone/laptop device -
Requester
: discovery-node
that wants to know about one identity
- Id_Agent
: discovery-node
that knows the info about the
identity, and is listening in Swarm Pss
in the topic
id_discovery
Flow when a discovery-node
receives an Id discover request:

flow00
Discovery flow:
discovery-node
receives an https petition from theuser
asking for an identity info, from now, thisdiscovery-node
will be theRequester
Requester
checks if already knows a fresh copy of the data packet of the identity- in case that has the data, checks that the
timestamp
is not too old - if the data is fresh, returns it and finishes the process
- if the identity data was not in its databases, ask to the network for it (following steps)
- in case that has the data, checks that the
Requester
createsQuery
packet asking for who is the relay of identityjohn@domain.eth
Requester
sends theQuery
packet into theSwarm Pss
network under the topicid_discovery
- the
Requester
waits a configured amount of time, if theAnswer
don’t comes inside that time window, returns an error msg through https to theuser
- the
- the
Id_Agent
server of that identity will receive theQuery
packet and will see that is a user under its umbrella Id_Agent
server will answer theAnswer
packet (with the proofs of validity, signature, etc) to theRequester
Requester
receives theAnswer
packet (verifies the signature), and now knows how to reach the Relay node ofjohn@domain.eth
, and can answer to theuser
Requester Id_Agent
+ +
| |
* 1 |
* 2 |
* 3 |
| 4 |
+--------------------------->+
| * 5
| 6 |
+<---------------------------+
* 7 |
| |
+ +
Each data packet that is sent over the network, goes with a
ProofOfWork
, and a Signature
of the emmiter.
// Service holds the data about a node service (can be a Relay, a NameServer, a DiscoveryNode, etc)
type Service struct {
IdAddr common.Address
KademliaAddr []byte // Kademlia address
PssPubK PubK // Public Key of the pss node, to receive encrypted data packets
Url string
Type string // TODO define type specification (relay, nameserver, etc)
Mode string // Active or Passive(gateway) (this only affects to discovery-node's type)
ProofService []byte // TODO ProofClaimService data type (to be defined)
}
// Query is the data packet that a node sends to discover data about one identity
type Query struct {
Version string // version of the protocol
MsgId string // random msg id, to identify and relate Query and Answer
AboutId common.Address // About Who is requesting data (about which identity address)
RequesterId common.Address
RequesterKAddr []byte // Kademlia address
RequesterPssPubK PubK // Public Key of the pss node requester, to receive encrypted data packets
InfoFrom []byte // TODO to be defined
Timestamp int64
Nonce uint64 // for the PoW
}
// Answer is the data packet that a node sends when answering to a Query data packet
type Answer struct {
Version string // version of the protocol
MsgId string // random msg id, to identify and relate Query and Answer
AboutId common.Address
FromId common.Address
AgentId Service
Services []Service
Timestamp int64
Signature []byte
}
go run *.go --config config0.yaml start
Notifications Server¶
Overview¶
Contents
Notification server¶
Debugging¶
Dump DB counter collection:
mongo notifications-server --eval "c=db.getCollection('counters'); c.find();"
Dump DB notification collection:
mongo notifications-server --eval "c=db.getCollection('notifications'); c.find();"
Get notifications with curl:
curl -XPOST --data '{"idAddr": "0xd9d6800a1b20ceebef5420f878bbd915f8b4ed85"}' "http://127.0.0.1:10000/api/unstable/notifications?afterid=6&beforeid=9" | jq
Zero Knowledge Proof¶
zkSNARKs related utilities.
Repo | Description |
---|---|
circom | zkSNARKs circut compiler |
circomlib | Circuit libraries implementedin in circom |
snarkjs | zkSNARKs Javascript implementation (setup, proof, verifier and witness generation) |
websnark | A fast zkSNARKs proof generator written in native Web Assembly |
Circom¶
Overview¶
Circom¶
Circom is a language designed to write arithmetic circuits that can be used in zero knowledge proofs.
In particular, it is designed to work in zksnarks JavaScript library.
Usage¶
Creation of a circuit. This is an example of a NAND door:
template NAND() {
signal private input a;
signal input b;
signal output out;
out <== 1 - a*b;
a*(a-1) === 0;
b*(b-1) === 0;
}
component main = NAND();
The language uses mainly JavaScript/C syntax together with 5 extra operators to define the following constraints:
<==
, ==>
: These two operators are used to connect signals and
at the same time imply a constraint.
As it is shown in the above example, a value is assigned to out
and
a constraint is also generated. The assigned value must be of the form
a*b+c where a,b and c are linear combinations of the signals.
<--
, -->
: These operators assign values to signals but do not
generate any constraints. This allows to assign to a signal any value
involving strange operations like shifts, divisions, modulo operations,
etc. In general, these operators are used together with a ===
operator in order to force the constraint.
===
: This operator defines a constraint. The constraint must be
simplificable to a constraint of the form a*b+c=0
, where a
,
b
and c
are linear combinations of the signals.
In the above example, both inputs are forced to be binary by adding the
constraints a*(a-1)===0
and b*(b-1) === 0
.
First of all, the compiler must be installed by typing:
npm install -g circom
The circuit is compiled with the following command:
circom mycircuit.circom -o mycircuit.json
The resulting output ( mycircuit.json
) can be used in the zksnarks
JavaScript library.
In this library one can do the trusted setup, create the proofs and verify them.
In many situations, one has to convert an input to its binary representation. Therefore, the circuits can be written this way:
template Num2Bits(n) {
signal input in;
signal output out[n];
var lc1=0;
for (var i = 0; i<n; i++) {
out[i] <-- (in >> i) & 1;
out[i] * (out[i] -1 ) === 0;
lc1 += out[i] * 2**i;
}
lc1 === in;
}
component main = Num2Bits(8)
First of all, note that templates can have parameters. This allows to create libraries with templates that generate circuits in parametric ways. In this case, the circuit has an output of 8 signals, but one can easily instantiate any circuit with any number of outputs.
The inputs and outputs are defined as arrays. The programm allows multidimensional arrays for signals and variables.
Then, the values are assigned to each of the signals. In this case, the
values are assigned without the constraint using the shift and &
operators: out[i] <-- (in >> i) & 1;
Afterwards, the constraints need to be defined. In this case, there is a big constraint of the form:
in === out[0]*2**0 + out[1]*2**1 + out[2]*2**2 + ... + out[n-1]*2**(n-1)
We do this by using a variable lc1
and adding each signal multiplied
by its coefficient. This variable does not hold a value at compilation
time, but it holds a linear combination and it is used in the last
constraint:
lc1 === in;
The last step is to force each output to be binary. This is done by adding the following constraint to each output:
out[i] * (out[i] -1 ) === 0;
Let’s now create a 32bits adder.
This operation could be done directly by adding a simple constraint
out === in1 + in2
, but doing this the operation would not be module
2**32
but r
, where r
is the range of the elliptic curve. In
the case of the zCash current implementation of zkSNARKs this number is
typically some prime close to 2**253.
So, the strategy we will follow will be to first convert a number to binary, then do the addition using the binary representation like in regular electronic circuits, and finally change it back to a number.
To do this, we create 3 files: bitify.circom
, binsum.circom
and
sum_test.circom
.
bitify.circom:
template Num2Bits(n) {
signal input in;
signal output out[n];
var lc1=0;
for (var i = 0; i<n; i++) {
out[i] <-- (in >> i) & 1;
out[i] * (out[i] -1 ) === 0;
lc1 += out[i] * 2**i;
}
lc1 === in;
}
template Bits2Num(n) {
signal input in[n];
signal output out;
var lc1=0;
for (var i = 0; i<n; i++) {
lc1 += in[i] * 2**i;
}
lc1 ==> out;
}
binsum.circom
/*
Binary sum
==========
This component creates a binary sum componet of ops operands and n bits each operand.
e is number of carries and it depends on the number of operands in the input.
Main Constraint:
in[0][0] * 2^0 + in[0][1] * 2^1 + ..... + in[0][n-1] * 2^(n-1) +
+ in[1][0] * 2^0 + in[1][1] * 2^1 + ..... + in[1][n-1] * 2^(n-1) +
+ ..
+ in[ops-1][0] * 2^0 + in[ops-1][1] * 2^1 + ..... + in[ops-1][n-1] * 2^(n-1) +
===
out[0] * 2^0 + out[1] * 2^1 + + out[n+e-1] *2(n+e-1)
To waranty binary outputs:
out[0] * (out[0] - 1) === 0
out[1] * (out[0] - 1) === 0
.
.
.
out[n+e-1] * (out[n+e-1] - 1) == 0
*/
/* This function calculates the number of extra bits in the output to do the full sum. */
function nbits(a) {
var n = 1;
var r = 0;
while (n-1<a) {
r++;
n *= 2;
}
return r;
}
template BinSum(n, ops) {
var nout = nbits((2**n -1)*ops);
signal input in[ops][n];
signal output out[nout];
var lin = 0;
var lout = 0;
var k;
var j;
for (k=0; k<n; k++) {
for (j=0; j<ops; j++) {
lin += in[j][k] * 2**k;
}
}
for (k=0; k<nout; k++) {
out[k] <-- (lin >> k) & 1;
// Ensure out is binary
out[k] * (out[k] - 1) === 0;
lout += out[k] * 2**k;
}
// Ensure the sum
lin === lout;
}
sumtest.circom:
include "bitify.circom"
include "binsum.circom"
template Adder() {
signal private input a;
signal input b;
signal output out;
component n2ba = Num2Bits(32);
component n2bb = Num2Bits(32);
component sum = BinSum(32,2);
component b2n = Bits2Num(32);
n2ba.in <== a;
n2bb.in <== b;
for (var i=0; i<32; i++) {
sum.in[0][i] <== n2ba.out[i];
sum.in[1][i] <== n2bb.out[i];
b2n.in[i] <== sum.out[i];
}
out <== b2n.out;
}
component main = Adder();
In this example we have shown how to design a top-down circuit with many subcircuits and how to connect them together. One can also see that auxiliary functions to do specific computations can be created.
Tutorial¶
Contents
circom and snarkjs tutorial¶
This tutorial will guide you in creating your first Zero Knowledge zkSnark circuit. It will navegate across the various techniques to write circuits and it will show you how to create proofs and verify them off-chain and on-chain on Ethereum.
1. Installing the tools¶
If you don’t have it installed yet, you need to install Node.js
in
your laptop.
Last stable version of Node.js
(Or 8.12.0) works just fine. But if
you install the latest current version Node.js
(10.12.0) you will
see significant performance increase. This is because last versions of
node includes Big Integer Libraries nativelly. The snarkjs
library
makes use of this feature if available, and this improves the
performance x10 (!).
2. Working with a circuit¶
Let’s create a circuit that tries to prove that you are able to factor a number!
- Create an empty directory called
factor
where you will put all the files that you will use in this tutorial.
mkdir factor
cd factor
In a real circuit, you will probably want to create agit
repository with acircuits
directory and atest
directory with all your tests, and the needed scripts to build all the circuits.
- Create a new file named
circuit.circom
with the following content:
template Multiplier() {
signal private input a;
signal private input b;
signal output c;
c <== a*b;
}
component main = Multiplier();
This circuit has 2 private input signals named a
and b
and one
output named c
.
The only thing that the circuit does is forcing the signal c
to be
the value of a*b
After declaring the Multiplier
template, we instantiate it with a
component namedmain
.
Note: When compiling a circuit a component named main
must always
exist.
We are now ready to compile the circuit. Run the following command:
circom circuit.circom -o circuit.json
to compile the circuit to a file named circuit.json
3. Taking the compiled circuit to snarkjs¶
Now that the circuit is compiled, we will continue with snarkjs
.
Please note that you can always access the help of snarkjs
by
typing:
snarkjs --help
To show general statistics of this circuit, you can run:
snarkjs info -c circuit.json
You can also print the constraints of the circuit by running:
snarkjs printconstraints -c circuit.json
Ok, let’s run a setup for our circuit:
snarkjs setup
By defaultsnarkjs
will look for and usecircuit.json
. You can always specify a different circuit file by adding-c <circuit JSON file name>
The output of the setup will in the form of 2 files:
proving_key.json
and verification_key.json
Before creating any proof, we need to calculate all the signals of the circuit that match (all) the constrains of the circuit.
snarkjs
calculates these for you. You need to provide a file with
the inputs and it will execute the circuit and calculate all the
intermediate signals and the output. This set of signals is the
witness.
The zero knowledge proofs prove that you know a set of signals (witness) that match all the constraints but without revealing any of the signals except the public inputs plus the outputs.
For example, Imagine that you want to prove that you are able to factor
33 that means that you know two numbers a
and b
that when you
multiply them, it results in 33.
Of course you can always use one and the same number asa
andb
. We will deal with this problem later.
So you want to prove that you know 3 and 11.
Let’s create a file named input.json
{"a": 3, "b": 11}
And now let’s calculate the witness:
snarkjs calculatewitness
You may want to take a look at witness.json
file with all the
signals.
Now that we have the witness generated, we can create the proof.
snarkjs proof
This command will use the prooving_key.json
and the witness.json
files by default to generate proof.json
and public.json
The proof.json
file will contain the actual proof. And the
public.json
file will contain just the values of the public inputs
and the outputs.
To verify the proof run:
snarkjs verify
This command will use verification_key.json
, proof.json
and
public.json
to verify that is valid.
Here we are veifying that we know a witness that the public inputs and
the outputs matches the ones in the public.json
file.
If the proof is ok, you will see an OK
in the screen or INVALID
otherwise.
snarkjs generateverifier
This command will take the verification_key.json
and generate a
solidity code in verifier.sol
file.
You can take the code in verifier.sol
and cut and paste in remix.
This code contains two contracts: Pairings and Verifier. You just need
to deploy the Verifier
contract.
You may want to use a test net like Rinkeby, Kovan or Ropsten. You can also use the Javascript VM, but in some browsers, the verification takes long and it may hang the page.
The verifier contract deployed in the last step has a view
function
called verifyProof
.
This function will return true if the proof and the inputs are valid.
To facilitiate the call, you can use snarkjs to generate the parameters of the call by typing:
snarkjs generatecall
Just cut and paste the output to the parameters field of the
verifyProof
method in Remix.
If every thing works ok, this method should return true.
If you just change any bit in the parameters, you can check that the result will be false.
Bonus track¶
We can fix the circuit to not accept one as any of the values by adding some extra constraints.
Here the trick is that we use the property that 0 has no inverse. so
(a-1)
should not have an inverse.
that means that (a-1)*inv = 1
will be inpossible to match if a
is one.
We just calculate inv by 1/(a-1)
So let’s modify the circuit:
template Multiplier() {
signal private input a;
signal private input b;
signal output c;
signal inva;
signal invb;
inva <-- 1/(a-1);
(a-1)*inva === 1;
invb <-- 1/(b-1);
(b-1)*invb === 1;
c <== a*b;
}
component main = Multiplier();
A nice thing of circom language is that you can split a <== into two independent acions: <– and ===
The <– and –> operators Just assign a value to a signal without creating any constraints.
The === operator just adds a constraint without assigning any value to any signal.
The circuit has also another problem and it’s that the operation works in Zr, so we need to guarantee too that the multiplication does not overflow. This can be done by binarizing the inputs and checking the ranges, but we will reserve it for future tutorials.
Where to go from here.¶
You may want to read the README to learn more features about circom.
You can also check a a library with many basic circuits lib binaritzations, comparators, eddsa, hashes, merkle trees etc here (Work in progress).
Or a exponentiation in the Baby Jub curve here (Work in progress).
Final note¶
There is nothing worst for a dev than working with a buggy compiler. This is a very early stage of the compiler, so there are many bugs and lots of works needs to be done. Please have it present if you are doing anything serious with it.
And please contact us for any isue you have. In general, a github issue with a small piece of code with the bug is very worthy!.
Enjoy zero knowledge proving!
CircomLib¶
SnarkJS¶
Overview¶
Contents
snarkjs: JavaScript implementation of zkSNARKs.¶
This is a JavaScript implementation of zkSNARK schemes. It allows the original 8points protocol and the Groth Protocol (3 point only and 3 pairings)
This library allows to do the trusted setup, generate proofs and verify the proofs.
This library uses the compiled circuits generated by the jaz compiler.
Tutorial.¶
A good starting point is this tutorial
Also this video is a good starting point.
npm install snarkjs
Load a circuit.¶
// "myCircuit.cir" is the output of the jaz compiler
const circuitDef = JSON.parse(fs.readFileSync("myCircuit.cir", "utf8"));
const circuit = new zkSnark.Circuit(circuitDef);
Inspect the circuit.¶
// `signalId` can always be a number or an alias string
circuit.nConstraints; // number of constraints
circuit.nSignals; // number of signals
circuit.nPublic; // number of public signals (nOutputs + nPublicInputs)
// The array of signals is always sorted in this order:
// [ 1, outputs, publicInputs, privateInputs, internalSignals, constants]
// returns a,b and c coeficients of the `signalId` on a given `constraint`
circuit.a(constraint, signalId)
circuit.b(constraint, signalId)
circuit.c(constraint, signalId)
circuit.nOutputs // number of public outputs
circuit.pubInputs // number of public inputs
circuit.nPrvInputs // number of private inputs
circuit.nInputs // number of inputs ( nPublicInputs + nPrivateInputs)
circuit.nVars // number of variables ( not including constants (one is a variable) )
circuit.nSignals // number of signals ( including constants )
circuit.outputIdx(i) // returns the index of the i'th output
circuit.inputIdx(i) // returns the index of the i'th input
circuit.pubInputIdx(i) // returns the index of the i'th public input
circuit.prvInputIdx(i) // returns the index of the i'th private input
circuit.varIdx(i) // returns the index of the i'th variable
circuit.constantIdx(i) // returns the index of the i'th constant
circuit.signalIdx(i) // returns the index of the i'th signal
// returns signal Idx given a signalId
// if the idx >= n , it is a constant
// if the idx == -1, the signal does not exist
circuit.getSignalIdx(name);
// returns an array aliases names of the i'th signal
circuit.signalNames(i)
// input is a key value object where keys are the signal names
// of all the inputs (public and private)
// returns an array of values representing the witness
circuit.calculateWitness(input)
Trusted setup.¶
const setup = zkSnark.setup(circuit);
fs.writeFileSync("myCircuit.vk_proof", JSON.stringify(setup.vk_proof), "utf8");
fs.writeFileSync("myCircuit.vk_verifier", JSON.stringify(setup.vk_verifier), "utf8");
setup.toxic // Must be discarded.
Generate proof.¶
const circuitDef = JSON.parse(fs.readFileSync("myCircuit.cir", "utf8"));
const circuit = new zkSnark.Circuit(circuitDef);
const input = {
"main.pubIn1": "123",
"main.out1": "456"
}
const witness = circuit.calculateWitness(input);
const vk_proof = JSON.parse(fs.readFileSync("myCircuit.vk_proof", "utf8"));
const {proof, publicSignals} = zkSnark.genProof(vk_proof, witness);
Verifier.¶
const vk_verifier = JSON.parse(fs.readFileSync("myCircuit.vk_verifier", "utf8"));
if (zkSnark.isValid(vk_verifier, proof, publicSignals)) {
console.log("The proof is valid");
} else {
console.log("The proof is not valid");
}
snarkjs is part of the iden3 project copyright 2018 0KIMS association and published with GPL-3 license. Please check the COPYING file for more details.
Websnark¶
Overview¶
websnark¶
A fast zkSnark proof generator written in native Web Assembly.
websnark is used to generate zkSnark Proofs from the browser.
This module generates highly optimized Web Assembly modules for the low level cryptographic primitives.
It also makes use of the Web Workers feature to parallelize the generation of the zero knoledge proofs.
The result is a fast library with times close to libsnarks but fully compatible for browsers.
Usage¶
You just need to import the websnark.js found in the build directory.
<script src="websnark.js" />
This library has a single javascript function:
genZKSnarkProof(witness, provingKey, cb)
cb is the callback. If cb is undefined, then the function will return a promise.
witness is a binary buffer with all the signals in binnary format. The buffer is packt in 32bytes Little Endian Encoded Field Elements.
You can use the tool to build the binary file from the witness.json file generated by snarkjs.
--protocol groth
websnark only generates groth16 proofs!¶node ../tools/buildwitness.js -i witness.json -o witness.bin
provingKey is the binary buffer with the binary representation of the proving key.
Check the tool tools/buildpkey.js to convert a proving_key.json file generated in snarkjs to a proving_key.bin file that can be used directly with this library.
node ../tools/buildpkey.js -i proving_key.json -o proving_key.bin
The result is a JSON object with pi_a, pi_b and pi_c points.
You can use the stringified version of this JSON as a proof.json in snarkjs
Here is a simple example of a web page that loads a key and a witness and generates the proof when the button is pressed.
<html>
<header>
</header>
<script src="websnark.js"></script>
<script>
var witness;
var proving_key;
function onLoad() {
fetch("proving_key.bin").then( (response) => {
return response.arrayBuffer();
}).then( (b) => {
provingKey = b;
});
fetch("witness.bin").then( (response) => {
return response.arrayBuffer();
}).then( (b) => {
witness = b;
});
}
function calcProof() {
const start = new Date().getTime();
document.getElementById("time").innerHTML = "processing....";
document.getElementById("proof").innerHTML = "";
window.genZKSnarkProof(witness, provingKey).then((p)=> {
const end = new Date().getTime();
const time = end - start;
document.getElementById("time").innerHTML = `Time to compute: ${time}ms`;
document.getElementById("proof").innerHTML = JSON.stringify(p, null, 1);
});
}
</script>
<body onLoad="onLoad()">
<h1>iden3</h1>
<h2>Zero knowledge proof generator</h2>
<button onClick="calcProof()">Test</button>
<div id="time"></div>
<pre id="proof"></pre>
</body>
</html>
You can test it by running a web server on the example directory
npm -g install http-server
cd example
http-server .
And then navegate to http://127.0.0.1:8080
The generated proof can be cut and pasted to example/proof
and
tested with snarkjs
snarkjs verify
``
## Building wasm.js
npm run build
## Testing
npm test ```
Misc¶
Miscellaneous utilities.
Repo | Description |
---|---|
wasmbuilder | Javascript library to simplify writing Web Assembly code |
Web Assembly Builder¶
Research Papers¶
Baby Jubjub¶
Contents
1.2
Scope¶
This proposal aims to define a specific elliptic curve defined over the large prime subgroup of BN128 elliptic curve.
Motivation¶
The search for this elliptic curve defined is motivated by its usefulness in zk-SNARK proofs. Moreover the ability to find it in a deterministic way—so that it was clear no other considerations were taken for defining—is paramount as it significantly reduces the possibility of a backdoor being present, thus leading to better security.
Background¶
With this purpose, we used a deterministic algorithm for finding elliptic curves over a specified finite field (Langley, Hamburg, and Turner, n.d.) together with the restrictions of security parameters described in SafeCurves project (Bernstein and Lange, n.d.).
Terminology And Description¶
Generation Of Baby Jubjub¶
Definition Of Baby Jubjub¶
From now on, let
elements.
Montgomery Form¶
We define \(E_M\) as the Baby-Jubjub Montgomery elliptic curve defined over \(\ensuremath{\mathbb{F}_p}\) given by equation
subgroup of points of order \(r\), that is,
Edwards Form¶
\[ \begin{align}\begin{aligned}E: x^2 + y^2 = 1 + d x^2 y^2\\where\end{aligned}\end{align} \]\(d = 9706598848417545097372247223557719406784115219466060233080913168975159366771.\)
\[ \begin{align}\begin{aligned}(x,y) \to (u,v) = \left( \frac{1 + y}{1 - y} , \frac{1 + y}{(1 - y)x} \right)\\with inverse from :math:`E_M` to :math:`E`\end{aligned}\end{align} \]\[(u, v) \to (x, y) = \left( \frac{u}{v}, \frac{u - 1}{u + 1} \right).\]
Arithmetic In Baby Jubjub¶
In this section we define how to operate in the elliptic curve group: the addition of points and multiplication of a point by a scalar (an element of \(\ensuremath{\mathbb{F}_p}\)).
Addition Of Points¶
When adding points of elliptic curves in Montgomery form, one has to be careful if the points being added are equal (doubling) or not (adding) and if one of the points is the point at infinity (Okeya, Kurumatani, and Sakurai 2000). Edwards curves have the advantage that there is no such case distinction and doubling can be performed with exactly the same formula as addition (Bernstein et al., n.d.). In comparison, operating in Montgomery curves is cheaper. In this section, we summarize how addition and doubling is performed in both forms. For the exact number of operations required in different forms of elliptic curves, see (Bernstein et al., n.d.).
: Let \(P_{1} = (x_{1}, y_{1})\) and \(P_{2} = (x_{2}, y_{2})\) be points of the Baby-Jubjub twisted Edwards elliptic curve \(E\). The sum \(P_1 + P_2\) is a third point \(P_3 = (x_3, y_3)\) with
\[ \begin{align}\begin{aligned}\begin{split} \begin{aligned} &\lambda = d x_1x_2y_1y_2,\\ &x_3 = (x_1y_2 + y_1x_2) / (1 + \lambda),\\ &y_3 = (y_1y_2 - x_1x_2) / (1 - \lambda). \end{aligned}\end{split}\\Note that the neutral element is the point :math:`O = (0,1)` and the\end{aligned}\end{align} \]inverse of a point \((x,y)\) is \((-x,y)\).
: Let \(P_{1} = (x_{1}, y_{1})\not=O\) and \(P_{2} = (x_{2}, y_{2})\not=O\) be two points of the Baby-JubJub elliptic curve \(E_M\) in Montgomery form.
If \(P_1\not=P_2\), then the sum \(P_1 + P_2\) is a third point \(P_3 = (x_3, y_3)\) with coordinates
\[ \begin{align}\begin{aligned}\begin{split} \begin{aligned} \label{eq-ted} \begin{split} &\Lambda = (y_2-y_1)/ (x_2-x_1),\\ &x_3 = \Lambda^2 - A - x_1 - x_2,\\ &y_3 = \Lambda(x_1- x_3) - y_1. \end{split} \end{aligned}\end{split}\\If :math:`P_1 = P_2`, then :math:`2\cdot P_1` is a point\end{aligned}\end{align} \]\(P_3 = (x_3, y_3)\) with coordinates
\[\begin{split}\begin{aligned} \label{eq-mont} \begin{split} &\Lambda = (3x_1^2 + 2Ax_1 + 1)/ (2y_1),\\ &x_3 = \Lambda^2 - A - 2x_1,\\ &y_3 = \Lambda(x_1- x_3) - y_1. \end{split} \end{aligned}\end{split}\]
Multiplication Of A Point Of \(E\) By A Scalar¶
Let \(P\not= O\) be a point of the Edwards curve \(E\) of order strictly greater than 8 (i.e. \(P\in\ensuremath{\mathbb{G}}\)) and let \(k\) a binary number representing an element of \(\ensuremath{\mathbb{F}_p}\). We describe the circuit used to compute the point \(k\cdot P\).
First, we divide \(k\) into chunks of 248 bits. If \(k\) is not a multiple of 248, we take \(j\) segments of 248 bits and leave a last chunk with the remaining bits. More precisly, write
\[ \begin{align}\begin{aligned}\begin{split} \begin{gathered} k = k_0 k_1 \dots k_j \quad\text{with}\quad \begin{cases} k_i = b^i_0 b^i_1 \dots b^i_{247} \;\text{ for } i = 0, \dots, j-1, \\ k_j = b^j_0 b^j_1 \dots b^j_s \;\text{ with } s\leq 247. \end{cases} \end{gathered}\end{split}\\Then,\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned} \label{kP} k\cdot P = k_0\cdot P + k_1\cdot 2^{248}P +\dots+ k_j\cdot 2^{248j}P.\\This sum is done using the following circuit. The terms of the sum\end{aligned}\end{align} \]are calculated separately inside the seq boxes and then added together.
Each seq box takes a point of \(E\) of the from \(P_i = 2^{248 i} P\) for \(i=0,\dots,j-1\) and outputs two points
\[ \begin{align}\begin{aligned} 2^{248} \cdot P_i \quad \text{and} \quad \sum_{n = 0}^{247} b_n \cdot 2^{n} \cdot P_i.\\The first point is the input of the next :math:`(i+1)`-th seq box\end{aligned}\end{align} \](note that \(2^{248} \cdot P_i = P_{i+1}\)) whereas the second output is the computation of the \(i\)-th term in expression ([kP]). The precise circuit is depicted in next two figures seq and window.
The idea of the circuit is to first compute
\[ \begin{align}\begin{aligned} Q = P_i + b_1 \cdot (2P_i) + b_2 \cdot (4P_i) + b_3 \cdot (8P_i) + \dots + b_{247} \cdot (2^{247}P_i),\\and output the point\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned}Q - b_0 \cdot P_i.\\This permits the computation of :math:`Q` using the Montgomery form\end{aligned}\end{align} \]of Baby-Jubjub and only use twisted Edwards for the second calculation. The reason to change forms is that, in the calculation of the output, we may get a sum with input the point at infinity if \(b_0 = 0\).
Still, we have to ensure that none of the points being doubled or added when working in \(E_M\) is the point at infinity and that we never add the same two points.
- By assumption, \(P\not= O\) and ord\((P)>8\). Hence, by Lagrange theorem (Baumslag and Chandler 1968 Corollary 4.12), \(P\) must have order \(r\), \(2r\), \(4r\) or \(8r\). For this reason, none of the points in \(E_M\) being doubled or added in the circuit is the point at infinity, because for any integer \(m\), \(2^m\) is never a multiple of \(r\), even when \(2^m\) is larger than \(r\), as \(r\) is a prime number. Hence, \(2^m \cdot P \not= O\) for any \(m\in\ensuremath{\mathbb{Z}}\).
- Looking closely at the two inputs of the sum, it is easy to realize that they have different parity, one is an even multiple of \(P_i\) and the other an odd multiple of \(P_i\), so they must be different points. Hence, the sum in \(E_M\) is done correctly.
The last term of expression ([kP]) is computed in a very similar manner. The difference is that the number of bits composing \(k_j\) may be shorter and that there is no need to compute \(P_{j+1}\), as there is no other seq box after this one. So, there is only output, the point \(k_j \cdot P_j = k_j\cdot 2^{248j} P\). This circuit is named seq’.
Challenges And Security¶
As required in the construction of Baby-Jubjub, the curve satisfies SafeCurves criteria. This can be checked following (Hat, n.d.).
Implementation¶
Barry WhiteHat:
Jordi Baylina: https://github.com/iden3/circomlib/blob/master/src/babyjub.js
Intellectual Property¶
We will release the final version of this proposal under creative commons, to ensure it is freely available to everyone.
Baumslag, Benjamin, and Bruce Chandler. 1968. Schaum’s Outline of Theory and Problems of Group Theory. Schaum’s Outline Series. New York: McGraw-Hill Book Company.
Bernstein, Daniel J., Peter Birkner, Marc Joye, Tanja Lange, and Christiane Peters. n.d. “Twisted Edwards Curves.” Cryptology ePrint Archive, Report 2008/013.
Bernstein, Daniel J., and Tanja Lange. n.d. “SafeCurves: Choosing Safe Curves for Elliptic-Curve Cryptography.”
Hat, Barry White. n.d. “Baby-Jubjub Supporting Evidence.” GitHub.
Langley, Adam, Mike Hamburg, and Sean Turner. n.d. “Elliptic Curves for Security.” Request for Comments. RFC 7748; RFC Editor. https://doi.org/10.17487/RFC7748.
Okeya, Katsuyuki, Hiroyuki Kurumatani, and Kouichi Sakurai. 2000. “Elliptic Curves with the Montgomery-Form and Their Cryptographic Applications.” In Proceedings of the Third International Workshop on Practice and Theory in Public Key Cryptography: Public Key Cryptography, 238–57. PKC ’00. London, UK, UK: Springer-Verlag. http://dl.acm.org/citation.cfm?id=648117.746614.
Merkle Tree¶
Contents
Scope¶
A Merkle tree or hash tree is an authenticated data structure where every leaf node of the tree contains the cryptographic hash of a data block and every non leaf node contains the concatenated hashes of its child nodes (Haider 2018). If the majority of the leaves are empty, then they are called sparse Merkle trees (Dahlberg, Pulls, and Peeters 2016). This proposal aims to standardize the generation of this second kind of binary trees.
Motivation¶
Merkle trees allow to link a set of data to a unique has value, which is very optimal and useful, specially in blockchain technology, as it provides a secure and efficient verification of large data sets by storing only a little piece of data on-chain. For instance, they can be used to verify any kind of data stored, handled and transferred in and between computers. They can help ensure that data blocks received from other peers in a peer-to-peer network are received undamaged and unaltered, and even to check that the other peers do not lie and send fake blocks (Wikipedians, n.d.).
Background¶
We are still working on the literature compending the state of the art of this area.
Terminology¶
The following concepts are definitions and properties we assume across the document.
- The leaves of the Merkle tree consist of key-value pairs
\((k,v)\). We distinguish three different nodes:
- Empty node: A vertex that stores the key and value zero.
- Leaf: A vertex with both empty children.
- Internal node: A vertex with at least one non-empty child. The value is and the key such. It has the hash of its children.
- A Merkle audit path for a leaf in a Merkle tree is the shortest list of additional nodes in the tree required to compute the root hash for that tree.
- If the root computed from the audit path matches the true root, then the audit path is a proof of membership for that leaf in the tree.
- Otherwise, it is a proof of non-membership for that leaf in the tree.
Challenges¶
Work in progress.
Description¶
\[ \begin{align}\begin{aligned}H_{path} = H(e) = H(1 || k || v).\\This array of bits is going to represent a path through the tree:\end{aligned}\end{align} \]starting by the less significant bit and from the root of \(T\), it descents the tree by taking the left edge if there is a 0 and right one if there is a 1.
If we go down the tree following the sequence 01111111… we get to the leaf containing the value 0704eaec of some \(e'\) with \(H'_{path}=0111110...\) . Comparing \(H_{path}\) and \(H'_{path}\), the 7th bit is the first different bit. This means, that we should go down to the 7th level and store there the entries as shown in next figure:
- Each leaf consists of a pair (\(H(1 || k || v)\), \(k||v\)).
- Each intermediate node of a pair (\(H(H_L||H_R)\), \(K_L||K_R\)), where \((H_L,K_L)\) is the key-value of its left child and \((H_L,K_L)\) the key-value of its right child.
\(H_{path} \gets \text{GetPath($e$)}\) \(b \gets \text{LeastSignificantBit($H_{Index}$)}\) \(r \gets e\) \(r \gets e\) \(H_{Index} \gets H_{Index}\backslash{b}\) \(b \gets \text{LeastSignificantBit($H_{Index}$)}\) \(e' \gets \text{GetEntryStoredIn($r$)}\) \(H'_{path} \gets \text{GetPath($e'$)}\) Find first bit \(b_j\) such that \(H_{path}(j) \not= H'_{path}(j)\) Leaf(\(b_0...b_j\))\(\gets e\) Leaf(\(b_0...b'_j\))\(\gets e'\) RecalculateIntermediateNodeValues(\(T\))
Security¶
The security of an audit path reduces to the collision resistance of the underlying hash function. For a proof, see (Dahlberg, Pulls, and Peeters 2016 Lemma 1).
Implementation¶
The standarisation of Merkle trees we proposed are described an implemented in GoLang and JavaScript by the iden3 team in the following repositories:
Some detailed examples are also provided in these repositories:
Intellectual Property¶
We will release the final version of this proposal under creative commons, to ensure it is freely available to everyone.
Dahlberg, Rasmus, Tobias Pulls, and Roel Peeters. 2016. “Efficient Sparse Merkle Trees: Caching Strategies and Secure (Non-)Membership Proofs.” Cryptology ePrint Archive, Report 2016/683.
Haider, Faraz. 2018. “Compact Sparse Merkle Trees.” Cryptology ePrint Archive, Report 2018/955.
Wikipedians, B. n.d. Data Structures. PediaPress. https://books.google.es/books?id=aYxSZurAGXEC.
[1] | If the hash function \(H\) does not return a binary number, binarize it later. |
ED-DSA¶
Contents
1.2
Scope¶
This proposal aims to standarize the elliptic curve signature scheme Edwards-curve Digital Signature Algorithm (EdDSA) for Baby Jubjub Edwards elliptic curve using MiMC-7 hash function.
Motivation¶
EdDSA is a variant of Schnorr’s signature scheme and it provides high performance on a variety of platforms (Josefsson and Liusvaara, n.d.).
Background¶
Terminology¶
The table below summarizes the terminology used across the document. Each element is explained in greater detail in the following sections.
Notation | Description |
---|---|
\(p\) | Prime number. |
\(\ensuremath{\mathbb{F}_p}\) | Finite field with \(p\) elements. |
\(E\) | Baby Jubjub elliptic curve (defined over \(Fp\)) in Edwards form. |
\(E_M\) | Baby Jubjub elliptic curve (defined over \(Fp\)) in Montgomery form. |
\(l\) | Large prime number dividing the order of Baby Jubjub. |
\(\ensuremath{\mathbb{F}_l}\) | Finite field with \(l\) elements. |
\(\ensuremath{\mathbb{G}}\) | Group of \(\ensuremath{\mathbb{F}_p}\) -rational points of order \(l\). |
\(B\) | Base point (generator of \(\ensuremath{\mathbb{G}}\)) of Baby Jubjub. |
\(A = (A_x, A_y)\) | Public key. \(A\) is a point on \(E\). |
\(k\) | Private key. |
\(M\) | Message. \(M\) is an element of \(\ensuremath{\mathbb{F}_l}\) . |
\((R,S) = ((R_x, R_y), S)\) | Signature on \(M\). \(R\) is a point on \(E\) and \(S\) and element of \(\ensuremath{\mathbb{F}_l}\) . |
\(H\) | Hash function MiMC-7. |
\(r\) | Number of rounds of MiMC-7. |
\(c_0, c_1, \dots, c_r\) | Constants used in MiMC-7. |
[tab:notation]
Baby-Jubjub¶
\[ \begin{align}\begin{aligned} p = 21888242871839275222246405745257275088548364 400416034343698204186575808495617\\and let :math:`\ensuremath{\mathbb{F}_p}` be the finite field with\end{aligned}\end{align} \]\(p\) elements. We define \(E_M\) as the Baby-Jubjub Montgomery elliptic curve defined over \(\ensuremath{\mathbb{F}_p}\) given by equation
\[ \begin{align}\begin{aligned}E: v^2 = u^3 + 168698u^2 + u.\\The order of :math:`E_M` is :math:`n = 8\times l`, where\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned} l = 2736030358979909402780800718157159386076813972 158567259200215660948447373041\\is a prime number. Denote by :math:`\ensuremath{\mathbb{G}}` the\end{aligned}\end{align} \]subgroup of points of order \(l\), that is,
\[ \begin{align}\begin{aligned}\ensuremath{\mathbb{G}}= \Set{ P \in E(\ensuremath{\mathbb{F}_p}) | l P = O }.\\Let\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned}\begin{split} \begin{aligned} B = (17777552123799933955779906779655732241715742912184938656739573121738514868268,\\ 2626589144620713026669568689430873010625803728049924121243784502389097019475)\end{aligned}\end{split}\\be a generator of :math:`\ensuremath{\mathbb{G}}`.\end{aligned}\end{align} \]
\[ \begin{align}\begin{aligned}E: x^2 + y^2 = 1 + d x^2 y^2\\where\end{aligned}\end{align} \]\(d = 9706598848417545097372247223557719406784115219466060233080913168975159366771.\)
\[ \begin{align}\begin{aligned}(x,y) \to (u,v) = \left( \frac{1 + y}{1 - y} , \frac{1 + y}{(1 - y)x} \right)\\with inverse from :math:`E_M` to :math:`E`\end{aligned}\end{align} \]\[(u, v) \to (x, y) = \left( \frac{u}{v}, \frac{u - 1}{u + 1} \right).\]
MiMC-7¶
\[E_k(x) = (F_{r-1} \circ F_{r-2} \circ ... \circ F_0)(x) \oplus k.\]
= [draw, minimum size=2em] = [pin edge=to-,thin,black]
As the random constants \(c_i\) do not need to be generated for every evaluation of MiMC-7, they are hard-coded into the implementation. The generation of these constants and the required number of rounds is described in section 6.2.
EdDSA¶
The description of this protocol is based in (Josefsson and Liusvaara, n.d.): Let the public key be a point \(A = (A_x, A_y)\in E\) of order \(l\) and \(M\) a message we wish to sign. The signature on \(M\) by \(A\) consists of a par \((R,S)\) where \(R = (R_x, R_y)\) is a point of order \(l\) of \(E\) and \(S\in\ensuremath{\mathbb{F}_l}\backslash\{0\}\) such that
Challenges and security¶
One of the main challenges to create this standard and to see it adopted by the community is to provide correct, usable, and well-maintained implementations in as many languages as possible. Some effort is also required to audit and verify code coming from the community and claiming to implement EdDSA for Baby Jubjub to prevent the propagation of potentially insecure implementations. Part of the work in progress of looking batch verification of short signatures. Lastly, the proposal as it stands uses MiMC-7 as hash function as it works very optimal inside circuits. We believe some work is required to determinate the security MiMC hash functions.
Implementation¶
In this section, we specify how each of the main operations in the following EdDSA circuit are computed:
Operations in the elliptic curve¶
Addition of points¶
When adding points of elliptic curves in Montgomery form, one has to be careful if the points being added are equal (doubling) or not (adding) and if one of the points is the point at infinity (Okeya, Kurumatani, and Sakurai 2000). Edwards curves have the advantage that there is no such case distinction and doubling can be performed with exactly the same formula as addition (Bernstein et al., n.d.). In comparison, operating in Montgomery curves is cheaper. In this section, we summarize how addition and doubling is performed in both forms. For the exact number of operations required in different forms of elliptic curves, see (Bernstein et al., n.d.).
: Let \(P_{1} = (x_{1}, y_{1})\) and \(P_{2} = (x_{2}, y_{2})\) be points of the Baby-Jubjub twisted Edwards elliptic curve \(E\). The sum \(P_1 + P_2\) is a third point \(P_3 = (x_3, y_3)\) with
\[ \begin{align}\begin{aligned}\begin{split} \begin{aligned} &\lambda = d x_1x_2y_1y_2,\\ &x_3 = (x_1y_2 + y_1x_2) / (1 + \lambda),\\ &y_3 = (y_1y_2 - x_1x_2) / (1 - \lambda). \end{aligned}\end{split}\\Note that the neutral element is the point :math:`O = (0,1)` and the\end{aligned}\end{align} \]inverse of a point \((x,y)\) is \((-x,y)\).
: Let \(P_{1} = (x_{1}, y_{1})\not=O\) and \(P_{2} = (x_{2}, y_{2})\not=O\) be two points of the Baby-JubJub elliptic curve \(E_M\) in Montgomery form.
If \(P_1\not=P_2\), then the sum \(P_1 + P_2\) is a third point \(P_3 = (x_3, y_3)\) with coordinates
\[ \begin{align}\begin{aligned}\begin{split} \begin{aligned} \label{eq-ted} \begin{split} &\Lambda = (y_2-y_1)/ (x_2-x_1),\\ &x_3 = \Lambda^2 - A - x_1 - x_2,\\ &y_3 = \Lambda(x_1- x_3) - y_1. \end{split} \end{aligned}\end{split}\\If :math:`P_1 = P_2`, then :math:`2\cdot P_1` is a point\end{aligned}\end{align} \]\(P_3 = (x_3, y_3)\) with coordinates
\[\begin{split}\begin{aligned} \label{eq-mont} \begin{split} &\Lambda = (3x_1^2 + 2Ax_1 + 1)/ (2y_1),\\ &x_3 = \Lambda^2 - A - 2x_1,\\ &y_3 = \Lambda(x_1- x_3) - y_1. \end{split} \end{aligned}\end{split}\]
Multiplication of a point of \(E\) by a scalar¶
Let \(P\not= O\) be a point of the Edwards curve \(E\) of order strictly greater than 8 (i.e. \(P\in\ensuremath{\mathbb{G}}\)) and let \(k\) a binary number representing an element of \(\ensuremath{\mathbb{F}_p}\). We describe the circuit used to compute the point \(k\cdot P\).
First, we divide \(k\) into chunks of 248 bits. If \(k\) is not a multiple of 248, we take \(j\) segments of 248 bits and leave a last chunk with the remaining bits. More precisly, write
\[ \begin{align}\begin{aligned}\begin{split} \begin{gathered} k = k_0 k_1 \dots k_j \quad\text{with}\quad \begin{cases} k_i = b^i_0 b^i_1 \dots b^i_{247} \;\text{ for } i = 0, \dots, j-1, \\ k_j = b^j_0 b^j_1 \dots b^j_s \;\text{ with } s\leq 247. \end{cases} \end{gathered}\end{split}\\Then,\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned} \label{kP} k\cdot P = k_0\cdot P + k_1\cdot 2^{248}P +\dots+ k_j\cdot 2^{248j}P.\\This sum is done using the following circuit. The terms of the sum\end{aligned}\end{align} \]are calculated separately inside the seq boxes and then added together.
Each seq box takes a point of \(E\) of the from \(P_i = 2^{248 i} P\) for \(i=0,\dots,j-1\) and outputs two points
\[ \begin{align}\begin{aligned} 2^{248} \cdot P_i \quad \text{and} \quad \sum_{n = 0}^{247} b_n \cdot 2^{n} \cdot P_i.\\The first point is the input of the next :math:`(i+1)`-th seq box\end{aligned}\end{align} \](note that \(2^{248} \cdot P_i = P_{i+1}\)) whereas the second output is the computation of the \(i\)-th term in expression ([kP]). The precise circuit is depicted in next two figures seq and window.
The idea of the circuit is to first compute
\[ \begin{align}\begin{aligned} Q = P_i + b_1 \cdot (2P_i) + b_2 \cdot (4P_i) + b_3 \cdot (8P_i) + \dots + b_{247} \cdot (2^{247}P_i),\\and output the point\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned}Q - b_0 \cdot P_i.\\This permits the computation of :math:`Q` using the Montgomery form\end{aligned}\end{align} \]of Baby-Jubjub and only use twisted Edwards for the second calculation. The reason to change forms is that, in the calculation of the output, we may get a sum with input the point at infinity if \(b_0 = 0\).
Still, we have to ensure that none of the points being doubled or added when working in \(E_M\) is the point at infinity and that we never add the same two points.
- By assumption, \(P\not= O\) and ord\((P)>8\). Hence, by Lagrange theorem (Baumslag and Chandler 1968 Corollary 4.12), \(P\) must have order \(r\), \(2r\), \(4r\) or \(8r\). For this reason, none of the points in \(E_M\) being doubled or added in the circuit is the point at infinity, because for any integer \(m\), \(2^m\) is never a multiple of \(r\), even when \(2^m\) is larger than \(r\), as \(r\) is a prime number. Hence, \(2^m \cdot P \not= O\) for any \(m\in\ensuremath{\mathbb{Z}}\).
- Looking closely at the two inputs of the sum, it is easy to realize that they have different parity, one is an even multiple of \(P_i\) and the other an odd multiple of \(P_i\), so they must be different points. Hence, the sum in \(E_M\) is done correctly.
The last term of expression ([kP]) is computed in a very similar manner. The difference is that the number of bits composing \(k_j\) may be shorter and that there is no need to compute \(P_{j+1}\), as there is no other seq box after this one. So, there is only output, the point \(k_j \cdot P_j = k_j\cdot 2^{248j} P\). This circuit is named seq’.
MiMC-7¶
The specifications we use in the hash are (we are working in explaining this section in greater detail):
- Number of rounds: \(r = \ceil*{\frac{\log_2l}{\log_27}} = 91.\)
- Inputs:
- Coordinates of the public key: (\(A_x, A_y\)).
- Coordinates of the point \(8R\): (\(R8_x, R8_y\)).
- Message \(M\).
- Number of inputs: 5.
- Generation of constants: https://github.com/iden3/circomlib/blob/master/src/mimc7.js.
Example and test vectors¶
Work in progress.
Existing implementations¶
Intellectual Property¶
We will release the final version of this proposal under creative commons, to ensure it is freely available to everyone.
Albrecht, Martin, Lorenzo Grassi, Christian Rechberger, Arnab Roy, and Tyge Tiessen. 2016. “MiMC: Efficient Encryption and Cryptographic Hashing with Minimal Multiplicative Complexity.” Cryptology ePrint Archive, Report 2016/492.
Baumslag, Benjamin, and Bruce Chandler. 1968. Schaum’s Outline of Theory and Problems of Group Theory. Schaum’s Outline Series. New York: McGraw-Hill Book Company.
Bernstein, Daniel J., Peter Birkner, Marc Joye, Tanja Lange, and Christiane Peters. n.d. “Twisted Edwards Curves.” Cryptology ePrint Archive, Report 2008/013.
Josefsson, S., and I. Liusvaara. n.d. “Edwards-Curve Digital Signature Algorithm (Eddsa).” Request for Comments. RFC 8032; RFC Editor. https://doi.org/10.17487/RFC8032.
Okeya, Katsuyuki, Hiroyuki Kurumatani, and Kouichi Sakurai. 2000. “Elliptic Curves with the Montgomery-Form and Their Cryptographic Applications.” In Proceedings of the Third International Workshop on Practice and Theory in Public Key Cryptography: Public Key Cryptography, 238–57. PKC ’00. London, UK, UK: Springer-Verlag. http://dl.acm.org/citation.cfm?id=648117.746614.
Pedersen Hash¶
Contents
Scope¶
The 4-bit window Pedersen hash function is a secure hash function which maps a sequence of bits to a compressed point on an elliptic curve (Libert, Mouhartem, and Stehlé, n.d.).
This proposal aims to standardize this hash function for use primarily within the arithmetic circuits of zero knowledge proofs, together with other generic uses such as for Merkle tree or any use cases requiring a secure hash function.
As part of the standard, the paper details the elliptic curve used for the hash function, the process to compute the Pedersen hash from a given sequence of bits, and the computation of the hash from a sequences of bits using an arithmetic circuit—which can be used within zero knowledge proofs.
Moreover the paper includes references to open-source implementations of the Pedersen hash function which follows the computation process details in this proposal.
Motivation¶
The primary advantage of this Pedersen hash function is its efficiency. The ability to compute the hash efficiently makes it an attractive proposal for use within the circuits associated with zk-SNARK proofs (“ZCash Open Discussion: Choose Improved Hash Function for Merkle Tree (or Replace Merkle Tree),” n.d.).
Having a standard, secure, and efficient hash function is one of the paramount aspect for implementing usable, comprehensible, and easily verifiable zero knowledge proofs.
Background¶
The Pedersen hash has already been defined and used by the ZCash team in Sapling, their latest network upgrade (Hopwood et al., n.d.). They construct it on the Jubjub elliptic curve and using 3-bit lookup tables. In this document, we propose a different implementation of the Pedersen hash function using Baby-Jubjub elliptic curve and 4-bit windows, which requires less constraints per bit than using 3-bit windows.
Terminology¶
Elliptic Curve: Baby-Jubjub¶
Consider the prime number
\(p\) elements.
We define \(E_M\) as the Baby-Jubjub Montgomery elliptic curve defined over \(\ensuremath{\mathbb{F}_p}\) given by equation
subgroup of points of order \(r\), that is,
\[ \begin{align}\begin{aligned}E: x^2 + y^2 = 1 + d x^2 y^2\\where\end{aligned}\end{align} \]\(d = 9706598848417545097372247223557719406784115219466060233080913168975159366771.\)
\[ \begin{align}\begin{aligned}(x,y) \to (u,v) = \left( \frac{1 + y}{1 - y} , \frac{1 + y}{(1 - y)x} \right)\\with inverse from :math:`E_M` to :math:`E`\end{aligned}\end{align} \]\[(u, v) \to (x, y) = \left( \frac{u}{v}, \frac{u - 1}{u + 1} \right).\]
Pedersen Hash¶
Let \(M\) be a sequence of bits. The Pedersen hash function of \(M\) is defined as follows:
Let \(P_0,P_1,\dots,P_k\) be uniformly sampled generators of \(\ensuremath{\mathbb{G}}\) (for some specified integer \(k\)).
Split \(M\) into sequences of at most 200 bits and each of those into chunks of 4 bits [1]. More precisely, write
\[ \begin{align}\begin{aligned}\begin{split} \begin{gathered} M = M_0M_1\dots M_l \quad\text{where}\quad M_i = m_0m_1\dots m_{k_i} \quad\text{with}\quad \begin{cases} k_i = 49 \;\text{ for } i = 0, \dots, l-1, \\ k_i \leq 49 \;\text{ for } i = l, \end{cases} \end{gathered}\end{split}\\where the :math:`m_j` terms are chunks of 4 bits\end{aligned}\end{align} \]\([b_0\: b_1\: b_2\: b_3]\). Define
\[ \begin{align}\begin{aligned} enc(m_j) = (2b_3-1) \cdot (1+b_{0}+2b_{1}+4b_{2})\\and let\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned}\langle M_i \rangle = \sum_{j=0}^{k_i-1} enc(m_j) \cdot 2^{5j}.\\We define the Pedersen hash of :math:`M` as\end{aligned}\end{align} \]\[ \begin{align}\begin{aligned} \label{eq-ped} H(M) = \langle M_0 \rangle \cdot P_0 + \langle M_1 \rangle \cdot P_1 + \langle M_2 \rangle \cdot P_2 + \dots + \langle M_l \rangle \cdot P_l.\\Note that the expression above is a linear combination of elements\end{aligned}\end{align} \]of \(\ensuremath{\mathbb{G}}\), so itself is also an element of \(\ensuremath{\mathbb{G}}\). That is, the resulting Pedersen hash \(H(M)\) is a point of the elliptic curve \(E\) of order \(r\).
Description¶
Set Of Generators¶
We generate the points \(P_0,\dots,P_{{k}}\) in such a manner that
it is difficult to find a connection between any of these two points.
More precisely, we take D = "string\_seed"
followed by a byte S
holding that smallest number that H = Keccak256(D || S)
results in a
point in the elliptic curve \(E\).
Computation Of The Pedersen Hash¶
In the following circuit pedersen hash, we have depicted the circuit used to compute the Pedersen hash of a message \(M\) described in equation [eq-ped]. Each multiplication box returns a term of the sum.
As the set of generators are fixed, we can precompute its multiples and use 4-bit lookup windows to select the right points. This is done as shown in the circuit called selector. This circuit receives 4-bit chunk input and returns a point. The first three bits are used to select the right multiple of the point and last bit decides the sign of the point. The sign determines if the \(x\)-coordinate should be taken positive or negative, as with Edwards curves, negating a point corresponds to the negation of its first coordinate.
[sec-computation]
Examples And Test Vectors¶
Work In Progress
Challenges¶
One of the main challenges to create this standard and to see it adopted by the community is to provide correct, usable, and well-maintained implementations in as many languages as possible.
Some effort is also required to audit and verify code coming from the community and claiming to implement the 4-bit window Pedersen hash function to prevent the propagation of potentially insecure implementations.
Finally, the proposal as it stands today includes the padding of the message \(M\) to a multiple of four bits. There are potentials issues with this approach where collisions can happen.
Security¶
Overflow Prevention¶
\[ \begin{align}\begin{aligned}n = 2^0 \times8 + 2^5 \times 8 + \left(2^5\right)^2 \times8 \dots + 2^{245}\times 8 .\\To avoid overflow, this number should be smaller than\end{aligned}\end{align} \]\((r-1)/2\). Indeed,
\[ \begin{align}\begin{aligned}\begin{split} \begin{aligned} \quad\; n & = 8 \times \sum_{ k = 0}^{49} 2^{5k} = 8 \times \frac{2^{250}-1}{2^5-1}\\ & = 466903585634339497675689455680193176827701551071131306610716064548036813064%\\\end{aligned}\end{split}\\and\end{aligned}\end{align} \]\[\begin{split}\begin{aligned} \frac{r-1}{2} &= 1368015179489954701390400359078579693038406986079283629600107830474223686520 \\ & > n.\\ \vspace{0.4cm}\end{aligned}\end{split}\]
Implementation¶
A Note On Efficency: Number Of Constraints Per Bit¶
- 3-lookup window : \((1+3+2)/3 = 2\) constraints per bit.
- 4-lookup window : \((1 +3+3)/4 = 1.75\) constraints per bit.
The specific constraints can be determined as follows: let the multiplexers of coordinates \(x\) and \(y\) be represented by the following look up tables:
\(s_2\) | \(s_1\) | \(s_0\) | \(out\) |
---|---|---|---|
0 | 0 | 0 | \(a_0\) |
0 | 0 | 1 | \(a_1\) |
0 | 1 | 0 | \(a_2\) |
0 | 1 | 1 | \(a_3\) |
1 | 0 | 0 | \(a_4\) |
1 | 0 | 1 | \(a_5\) |
1 | 1 | 0 | \(a_6\) |
1 | 1 | 1 | \(a_7\) |
\(s_2\) | \(s_1\) | \(s_0\) | \(out\) |
---|---|---|---|
0 | 0 | 0 | \(b_0\) |
0 | 0 | 1 | \(b_1\) |
0 | 1 | 0 | \(b_2\) |
0 | 1 | 1 | \(b_3\) |
1 | 0 | 0 | \(b_4\) |
1 | 0 | 1 | \(b_5\) |
1 | 1 | 0 | \(b_6\) |
1 | 1 | 1 | \(b_7\) |
We can express them with the following 3 constraints:
\(aux = s_0 s_1\)
- \(out = [ (a_7-a_6-a_5+a_4-a_3+a_2+a_1-a_0)*aux + (a_6-a_4-a_2+a_0)*s_1\)\(\text{\qquad\;\;} + (a_5-a_4-a_1+a_0)*s_0 + (a_4 - a_0) ] z + (a_3-a_2-a_1+a_0)*aux + (a_2-a_0)*s_1\)\(\text{\qquad\;\;} + (a_1-a_0)*s_0+ a_0\)
- \(out = [ (b_7-b_6-b_5+b_4-b_3+b_2+b_1-b_0)*aux + (b_6-b_4-b_2+b_0)*s_1\)\(\text{\qquad\;\;} + (b_5-b_4-b_1+b_0)*s_0 + (b_4 - b_0)] z + (b_3-b_2-b_1+b_0)*aux + (b_2-b_0)*s_1 \\ \text{\qquad\;\:} + (b_1-b_0)*s_0+ b_0\)
Existing Implementations¶
Implementation of the specifications and arithmetic of the Baby-Jubjub curve:
- Barry WhiteHat (SAGE): https://github.com/barryWhiteHat/baby_jubjub.
- Jordi Baylina (circom language): https://github.com/iden3/circomlib/blob/master/circuits/babyjub.circom.
Implementation of the Pedersen Hash function:
- Jordi Baylina (circom language): https://github.com/iden3/circomlib/blob/master/circuits/.
Intellectual Property¶
The source code of the implementations listed in this proposal are publicly available. Circom is licensed under GPL3.
Bernstein, Daniel J., Peter Birkner, Marc Joye, Tanja Lange, and Christiane Peters. n.d. “Twisted Edwards Curves.” Cryptology ePrint Archive, Report 2008/013.
Hopwood, Daira, Sean Bowe, Taylor Hornby, and Nathan Wilcox. n.d. “ZCash Protocol Specification Version 2018.0-Beta-31.”
Libert, B., F. Mouhartem, and D. Stehlé. n.d. “Tutorial 8.”
“ZCash Open Discussion: Choose Improved Hash Function for Merkle Tree (or Replace Merkle Tree).” n.d.
[1] | If \(M\) is not a multiple of 4, pad \(M\) to a multiple of 4 bits by appending zero bits. |
Presentations¶
Links¶
Iden3 Overview
: Scalable distrubted identity system on ZKPCircom and SnarkJS
: Circom and SnarkJS overview