# Sidetree for Ruby [![Build Status](https://github.com/azuchi/sidetreerb/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/azuchi/sidetreerb/actions/workflows/main.yml) [![Gem Version](https://badge.fury.io/rb/sidetree.svg)](https://badge.fury.io/rb/sidetree) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) This is an experimental library that implements the [Sidetree protocol](https://identity.foundation/sidetree/spec/) in Ruby. If you use this library, please test to see if it works properly with [reference implementation](https://github.com/decentralized-identity/sidetree). ## Installation Add this line to your application's Gemfile: ```ruby gem 'sidetree' ``` And then execute: $ bundle install Or install it yourself as: $ gem install sidetree ## Usage ### Environment Currently, this library support mainnet and testnet. If you want to run with testnet, configure the network with the following code. ```ruby Sidetree::Params.network = Sidetree::Params::Network::TESTNET ``` ### Create new DID ```ruby # Generate recovery key recovery_key = Sidetree::Key.generate # Generate update key update_key = Sidetree::Key.generate # Generate signing key signing_key = Sidetree::Key.generate(id: "signing-key") # Create DID Document from signing key document = Sidetree::Model::Document.new(public_keys: [signing_key]) # Create DID did = Sidetree::DID.create( document, update_key, recovery_key ) => "did:sidetree:test:EiAw8oTzWzmPyTANALvzeHkCc6-uQ7XxnohqphrwC_jhNg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJzaWduaW5nLWtleSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJJQ3BuZXVJZ2ZqZW5UZzhhMmVCeEd1RkVjV1lwZkFTcDRXdHVmbmdGd3owIiwieSI6IlNPbUxzSzhIWlU0bmY5YWZCQmF0SlJUSjVWeGZSMlVRZ1hFVV8xTXVyTzgifSwicHVycG9zZXMiOltdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W119fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUExQ1l4NWE5NTlPRGRicThrMG1WUDJwZmVEY0RXaHd3TXhWamNDS2tZOXF3In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlDWklRdjlyeVVDR2dLbjMtSjNfTV9ZN0k1VTIyZlpldFRaSTFxSjZqdnVEQSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQ2dPTGRPb0F5a3A4SzBrMlNwMmlzRUtNOFpmRnBVSWhVcnRLbUEydEcxcWcifX0" ``` ### DID operation #### Create Operation ```ruby # Generate Create Operation create_op = did.create_op # Generate Chunk File from create operation chunk_file = Sidetree::Model::ChunkFile.create_from_ops(create_ops: [create_op]) # Write chunk file to IPFS # Initialize IPFS client(If the parameter is omitted, the default is to access http://localhost:5001/api/v0.) ipfs = Sidetree::CAS::IPFS.new chunk_file_uri = ipfs.write(chunk_file.to_compress) # Generate Provisional Index File and write to IPFS provisional_index_file = Sidetree::Model::ProvisionalIndexFile.new(chunks: [Sidetree::Model::Chunk.new(chunk_file_uri)]) provisional_index_file_uri = ipfs.write(provisional_index_file.to_compress) # Generate Core Index File and write to IPFS core_index_file = Sidetree::Model::CoreIndexFile.new( create_ops: [create_op], provisional_index_file_uri: provisional_index_file_uri ) core_index_file_uri = ipfs.write(core_index_file.to_compress) # Generate Anchor String to anchoring to Bitcoin anchor_str = Sidetree::Util::AnchoredDataSerializer.serialize(1, core_index_file_uri) ``` #### Update Operation ```ruby # DID suffix to be updated did_suffix = "EiBRrmEha_Q30GieEwLB-XM8CZd_b49dQ7znhaBxfAHTsQ" # Current update key update_key = Sidetree::Key.new(private_key: ) # Generate new signing key new_signing_key = Sidetree::Key.generate(id: "signing-key") # Create replace DID Document document = Sidetree::Model::Document.new(public_keys: [new_signing_key]) # Generate next update key next_update_key = Sidetree::Key.generate # Generate update delta object delta = Sidetree::Model::Delta.new( [document.to_replace_patch], next_update_key.to_commitment ) # Generate jws and sign with update key claim = { updateKey: update_key.to_jwk.normalize, deltaHash: delta.to_hash } jws = Sidetree::Util::JWS.sign(claim, update_key) # Generate Provisional Proof File and write to IPFS provisional_proof = Sidetree::Model::ProvisionalProofFile.new([jws]) provisional_proof_uri = ipfs.write(provisional_proof.to_compress) # Create update operation update_op = Sidetree::OP::Update.new(did_suffix, delta, jws, update_key.to_reveal_value) # Generate Chunk File and write to IPFS chunk_file = Sidetree::Model::ChunkFile.create_from_ops(update_ops: [update_op]) chunk_file_uri = ipfs.write(chunk_file.to_compress) # Generate Provisional Index File and write to IPFS provisional_index_file = Sidetree::Model::ProvisionalIndexFile.new( proof_file_uri: provisional_proof_uri, chunks: [Sidetree::Model::Chunk.new(chunk_file_uri)], operations: [update_op] ) provisional_index_file_uri = ipfs.write(provisional_index_file.to_compress) # Generate Core Index File and write to IPFS core_index_file = Sidetree::Model::CoreIndexFile.new( provisional_index_file_uri: provisional_index_file_uri ) core_index_file_uri = ipfs.write(core_index_file.to_compress) anchor_str = Sidetree::Util::AnchoredDataSerializer.serialize(1, core_index_file_uri) ``` #### Recover Operation ```ruby did_suffix = "EiBRrmEha_Q30GieEwLB-XM8CZd_b49dQ7znhaBxfAHTsQ" # Current recovery key recovery_key = Sidetree::Key.new(private_key: ) # Generate new recovery key new_recovery_key = Sidetree::Key.generate # Generate new update key new_update_key = Sidetree::Key.generate # Generate new signing key new_signing_key = Sidetree::Key.generate(id: "signing-key") # Generate replace DID Document document = Sidetree::Model::Document.new(public_keys: [new_signing_key]) delta = Sidetree::Model::Delta.new( [document.to_replace_patch], new_update_key.to_commitment ) # Generate jws and sign with recovery key claim = { recoveryKey: recovery_key.to_jwk.normalize, recoveryCommitment: new_recovery_key.to_commitment, deltaHash: delta.to_hash } jws = Sidetree::Util::JWS.sign(claim, recovery_key) # Generate Core Proof File from jws core_proof_file = Sidetree::Model::CoreProofFile.new(recover_proofs: [jws]) core_proof_file_uri = ipfs.write(core_proof_file.to_compress) # Create recover operation recover_op = Sidetree::OP::Recover.new( did_suffix, delta, jws, recovery_key.to_reveal_value ) # Generate Chunk File and write to IPFS chunk_file = Sidetree::Model::ChunkFile.create_from_ops(recover_ops: [recover_op]) chunk_file_uri = ipfs.write(chunk_file.to_compress) # Generate Provisional Index File and write to IPFS provisional_index_file = Sidetree::Model::ProvisionalIndexFile.new( chunks: [Sidetree::Model::Chunk.new(chunk_file_uri)] ) provisional_index_file_uri = ipfs.write(provisional_index_file.to_compress) # Generate Core Index File and write to IPFS core_index_file = Sidetree::Model::CoreIndexFile.new( provisional_index_file_uri: provisional_index_file_uri, core_proof_file_uri: core_proof_file_uri, recover_ops: [recover_op] ) core_index_file_uri = ipfs.write(core_index_file.to_compress) anchor_str = Sidetree::Util::AnchoredDataSerializer.serialize(1, core_index_file_uri) ``` #### Deactivate operation ```ruby # Current recovery key recovery_key = Sidetree::Key.new(private_key: ) # Generate jws and sign with recovery key claim = { didSuffix: did_suffix, recoveryKey: recovery_key.to_jwk.normalize } jws = Sidetree::Util::JWS.sign(claim, recovery_key) # Generate Core Proof File and write to IPFS core_proof_file = Sidetree::Model::CoreProofFile.new(deactivate_proofs: [jws]) core_proof_file_uri = ipfs.write(core_proof_file.to_compress) # Create Deactivate Operation deactivate_op = Sidetree::OP::Deactivate.new( did_suffix, jws, recovery_key.to_reveal_value ) # Generate Core Index File and write to IPFS core_index_file = Sidetree::Model::CoreIndexFile.new( core_proof_file_uri: core_proof_file_uri, deactivate_ops: [deactivate_op] ) core_index_file_uri = ipfs.write(core_index_file.to_compress) anchor_str = Sidetree::Util::AnchoredDataSerializer.serialize(1, core_index_file_uri) ``` The pseudocode above all anchor a single operation, but you can be combined into a batch(i.e. to single anchor string). ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sidetree. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/sidetree/blob/master/CODE_OF_CONDUCT.md). ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). ## Code of Conduct Everyone interacting in the Sidetree project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/sidetree/blob/master/CODE_OF_CONDUCT.md).