diff --git a/cmd/hid-noded/cmd/debug_extensions.go b/cmd/hid-noded/cmd/debug_extensions.go
index 883579f..1d6c68c 100644
--- a/cmd/hid-noded/cmd/debug_extensions.go
+++ b/cmd/hid-noded/cmd/debug_extensions.go
@@ -35,6 +35,7 @@ func secp256k1Cmd() *cobra.Command {
cmd.AddCommand(
secp256k1RandomCmd(),
+ secp256k1Bech32AddressCmd(),
secp256k1EthRandomCmd(),
)
@@ -79,6 +80,30 @@ func secp256k1RandomCmd() *cobra.Command {
return cmd
}
+func secp256k1Bech32AddressCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "bech32-addr [base64-encoded-public-key] [prefix]",
+ Short: "Converts a compressed base64 encoded secp256k1 public key to bech32 address",
+ Args: cobra.ExactArgs(2),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ base64PubKey := args[0]
+ addrPrefix := args[1]
+
+ publicKeyBytes, err := base64.StdEncoding.DecodeString(base64PubKey)
+ if err != nil {
+ panic(err)
+ }
+
+ bech32address := publicKeyToBech32Address(addrPrefix, publicKeyBytes)
+
+ _, err = fmt.Fprintln(cmd.OutOrStdout(), bech32address)
+ return err
+ },
+ }
+
+ return cmd
+}
+
func secp256k1EthRandomCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "eth-hex-random",
diff --git a/cmd/hid-noded/cmd/debug_extensions_utils.go b/cmd/hid-noded/cmd/debug_extensions_utils.go
new file mode 100644
index 0000000..47e6e1b
--- /dev/null
+++ b/cmd/hid-noded/cmd/debug_extensions_utils.go
@@ -0,0 +1,31 @@
+package cmd
+
+import (
+ "crypto/sha256"
+ "fmt"
+
+ "golang.org/x/crypto/ripemd160" //nolint: staticcheck
+
+ bech32 "github.com/cosmos/cosmos-sdk/types/bech32"
+)
+
+// publicKeyToBech32Address converts publicKey byteArray to Bech32 encoded blockchain address
+func publicKeyToBech32Address(addressPrefix string, pubKeyBytes []byte) string {
+ // Throw error if the length of secp256k1 publicKey is not 33
+ if len(pubKeyBytes) != 33 {
+ panic(fmt.Sprintf("invalid secp256k1 public key length %v", len(pubKeyBytes)))
+ }
+
+ // Hash pubKeyBytes as: RIPEMD160(SHA256(public_key_bytes))
+ pubKeySha256Hash := sha256.Sum256(pubKeyBytes)
+ ripemd160hash := ripemd160.New()
+ ripemd160hash.Write(pubKeySha256Hash[:])
+ addressBytes := ripemd160hash.Sum(nil)
+
+ // Convert addressBytes to bech32 encoded address
+ address, err := bech32.ConvertAndEncode(addressPrefix, addressBytes)
+ if err != nil {
+ panic(err)
+ }
+ return address
+}
diff --git a/go.mod b/go.mod
index d5eabd9..f770eaf 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
github.com/tendermint/spm v0.1.9
github.com/tendermint/tendermint v0.34.23
github.com/tendermint/tm-db v0.6.7
+ golang.org/x/crypto v0.1.0
google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a
google.golang.org/grpc v1.50.1
)
@@ -115,7 +116,6 @@ require (
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/zondax/hid v0.9.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
- golang.org/x/crypto v0.1.0 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.5.0 // indirect
diff --git a/tests/e2e/ssi_tests/README.md b/tests/e2e/ssi_tests/README.md
index 80160b0..3a40c51 100644
--- a/tests/e2e/ssi_tests/README.md
+++ b/tests/e2e/ssi_tests/README.md
@@ -1,19 +1,3 @@
-# SSI Module E2E Tests
-
-Following scenarios are covered for E2E testing:
-
-`TC-1`: A simple SSI flow where three elements of SSI (DID Document, Schema Document and Credential Status Document).
-`TC-2`: Controller DID attempts to create Credential Schema and Credential Status Documents on behalf of Parent DID.
-`TC-3`: Multiple Controller DID attempt to create Credential Schema and Credential Status Documents on behalf of Parent DID.
-`TC-4`: Non-Controller DID attempts to create Credential Schema and Credential Status Documents on behalf of Parent DID (Invalid Case).
-`TC-5`: Non-Controller DID attempts to update a DID Document (Invalid Case).
-`TC-6`: Controller DID attempts to update the Parent DID Document.
-`TC-7`: Parent DID adds multiple DIDs in its controller group, and removes itself. One of the controllers and the Parent DID attemps to change the DID Document.
-`TC-8`: Deactivated DID attempts to create Schema and Credential Status Documents.
-`TC-9`: `x/ssi` module related transactions, using `secp256k1` keypair.
-`TC-10`: Test scenarios for `blockchainAccountId`.
-`TC-11`: `x/ssi` module related transactions, using ethereum based `secp256k1` keypair.
-
## Run Tests
Run the following to run tests
diff --git a/tests/e2e/ssi_tests/e2e_tests.py b/tests/e2e/ssi_tests/e2e_tests.py
index 4bf4710..5b32a37 100644
--- a/tests/e2e/ssi_tests/e2e_tests.py
+++ b/tests/e2e/ssi_tests/e2e_tests.py
@@ -3,7 +3,7 @@
sys.path.insert(1, os.getcwd())
import time
-from utils import run_blockchain_command, generate_key_pair
+from utils import run_blockchain_command, generate_key_pair, secp256k1_pubkey_to_address
from generate_doc import generate_did_document, generate_schema_document, generate_cred_status_document
from transactions import form_did_create_tx_multisig, form_did_update_tx_multisig, \
query_did, form_create_schema_tx, form_did_deactivate_tx_multisig, form_create_cred_status_tx
@@ -141,7 +141,7 @@ def create_did_test():
create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
run_blockchain_command(create_tx_cmd, f"Registering DID by passing both Alice's and Eve's Signature")
- print("\n--- Test Completed ---\n")
+ print("--- Test Completed ---\n")
# TC - II : Update DID scenarios
def update_did_test():
@@ -353,7 +353,7 @@ def update_did_test():
update_tx_cmd = form_did_update_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
run_blockchain_command(update_tx_cmd, f"Jenny (controller) attempts to update Tx")
- print("\n--- Test Completed ---\n")
+ print("--- Test Completed ---\n")
def deactivate_did():
print("\n--- Deactivate DID Test ---\n")
@@ -431,7 +431,9 @@ def deactivate_did():
signers = []
signers.append(signPair_mike)
deactivate_tx_cmd = form_did_deactivate_tx_multisig(did_doc_mike, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
- run_blockchain_command(deactivate_tx_cmd, f"Deactivation of Mike's DID with Id: {did_doc_mike}")
+ run_blockchain_command(deactivate_tx_cmd, f"Deactivation of Mike's DID with Id: {did_doc_mike}")
+
+ print("--- Test Completed ---\n")
def schema_test():
print("\n--- Schema Test ---\n")
@@ -505,7 +507,7 @@ def schema_test():
schema_doc_id = schema_doc["id"]
run_blockchain_command(create_schema_cmd, f"Registering Schema with Id: {schema_doc_id}")
- print("\n--- Test Completed ---\n")
+ print("--- Test Completed ---\n")
def credential_status_test():
print("\n--- Credential Status Test ---\n")
@@ -579,4 +581,291 @@ def credential_status_test():
cred_id = cred_doc["claim"]["id"]
run_blockchain_command(register_cred_status_cmd, f"Registering Credential status with Id: {cred_id}")
- print("\n--- Test Completed ---\n")
\ No newline at end of file
+ print("--- Test Completed ---\n")
+
+def caip10_ethereum_support_test():
+ print("\n--- CAIP-10 Test: Ethereum Chains ---\n")
+ kp_algo = "recover-eth"
+
+ # Invalid blockchain Account Ids
+ invalid_blockchain_account_ids = [
+ "abc345566",
+ "eip:1:0x1234",
+ "eip155",
+ "eip155:1:",
+ "eip155::",
+ "eip155:1",
+ "eip155:::::23",
+ "eip155::0x1234567"
+ ]
+
+ for invalid_blockchain_id in invalid_blockchain_account_ids:
+ print("Registering a DID Document with an invalid blockchainAccountId:", invalid_blockchain_id)
+ kp = generate_key_pair(algo=kp_algo)
+
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"] = invalid_blockchain_id
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering a DID Document with an invalid blockchainAccountId: {invalid_blockchain_id}", True)
+
+ print("Registering a DID with a VM of type EcdsaSecp256k1SignatureRecovery2020 having publicKeyMultibase attribute populated")
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ did_doc_string["verificationMethod"][0]["publicKeyMultibase"] = "zrxxgf1f9xPYTraixqi9tipLta61hp4VJWQUUW5pmwcVz"
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, "Registering a DID with a VM of type EcdsaSecp256k1SignatureRecovery2020 having publicKeyMultibase attribute populated", True, True)
+
+ # Test for valid blockchainAccountId
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ print(
+ "Registering a DID Document with a valid blockchainAccountId:",
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"]
+ )
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}")
+
+ print("--- Test Completed ---\n")
+
+def caip10_cosmos_support_test():
+ print("\n--- CAIP-10 Test: Cosmos Chains ---\n")
+
+ kp_algo = "secp256k1"
+
+ # Invalid blockchain Account Ids
+ invalid_blockchain_account_ids = [
+ "abc345566",
+ "cos:1:0x1234",
+ "cosmos",
+ "cosmos:1",
+ "cosmos:jagrat",
+ "cosmos:1:",
+ "cosmos::",
+ "cosmos:1",
+ "cosmos:::::23",
+ "cosmos::0x1234567"
+ ]
+
+ for invalid_blockchain_id in invalid_blockchain_account_ids:
+ print("Registering a DID Document with an invalid blockchainAccountId:", invalid_blockchain_id)
+ kp = generate_key_pair(algo=kp_algo)
+
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"] = invalid_blockchain_id
+
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering a DID Document with an invalid blockchainAccountId: {invalid_blockchain_id}", True)
+
+ print("Registering a DID with a VM of type EcdsaSecp256k1VerificationKey2019 having both publicKeyMultibase and blockchainAccountId attributes populated")
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering the DID with DID Id {did_doc_id}")
+
+ print("Registering a DID with invalid chain-id in blockchainAccountId")
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ blockchainAccountId = secp256k1_pubkey_to_address(kp["pub_key_base_64"], "hid")
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"] = "cosmos:hidnode02:" + blockchainAccountId
+
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}", True)
+
+ print("--- Test Completed ---\n")
+
+def vm_type_test():
+ print("\n--- Verification Method Types Test ---\n")
+
+ # Ed25519VerificationKey2020
+ print("1. FAIL: Registering DID Document with a verification method of type Ed25519VerificationKey2020. Both publicKeyMultibase and blockchainAccountId are passed.")
+ kp_alice = generate_key_pair()
+ signers = []
+ did_doc_string = generate_did_document(kp_alice)
+ did_doc_alice = did_doc_string["id"]
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"] = "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv"
+ signPair_alice = {
+ "kp": kp_alice,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": "ed25519"
+ }
+ signers.append(signPair_alice)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_alice}", True, True)
+
+ print("2. FAIL: Registering DID Document with a verification method of type Ed25519VerificationKey2020. Only blockchainAccountId is passed.")
+ kp_alice = generate_key_pair()
+ signers = []
+ did_doc_string = generate_did_document(kp_alice)
+ did_doc_alice = did_doc_string["id"]
+ did_doc_string["verificationMethod"][0]["publicKeyMultibase"] = ""
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"] = "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv"
+ signPair_alice = {
+ "kp": kp_alice,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": "ed25519"
+ }
+ signers.append(signPair_alice)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_alice}", True, True)
+
+ print("3. PASS: Registering DID Document with a verification method of type Ed25519VerificationKey2020. Only publicKeyMultibase is passed.")
+ kp_alice = generate_key_pair()
+ signers = []
+ did_doc_string = generate_did_document(kp_alice)
+ did_doc_alice = did_doc_string["id"]
+ signPair_alice = {
+ "kp": kp_alice,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": "ed25519"
+ }
+ signers.append(signPair_alice)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_alice}")
+
+ # EcdsaSecp256k1VerificationKey2019
+ print("4. PASS: Registering DID Document with a verification method of type EcdsaSecp256k1VerificationKey2019. Only publicKeyMultibase is passed.")
+ kp_algo = "secp256k1"
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"] = ""
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}")
+
+ print("5. PASS: Registering DID Document with a verification method of type EcdsaSecp256k1VerificationKey2019. Both publicKeyMultibase and blockchainAccountId are passed.")
+ kp_algo = "secp256k1"
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}")
+
+ print("6. FAIL: Registering DID Document with a verification method of type EcdsaSecp256k1VerificationKey2019. Only blockchainAccountId is passed.")
+ kp_algo = "secp256k1"
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ did_doc_string["verificationMethod"][0]["publicKeyMultibase"] = ""
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}", True, True)
+
+ # EcdsaSecp256k1RecoveryMethod2020
+ print("7. FAIL: Registering DID Document with a verification method of type EcdsaSecp256k1RecoveryMethod2020. Only publicKeyMultibase is passed.")
+ kp_algo = "recover-eth"
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ did_doc_string["verificationMethod"][0]["publicKeyMultibase"] = "z22XxPVrzTr24zUBGfZiZCm9bwj3RkHKLmx9ENYBxmY57o"
+ did_doc_string["verificationMethod"][0]["blockchainAccountId"] = ""
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}", True, True)
+
+ print("8. FAIL: Registering DID Document with a verification method of type EcdsaSecp256k1RecoveryMethod2020. Both publicKeyMultibase and blockchainAccountId is passed.")
+ kp_algo = "recover-eth"
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ did_doc_string["verificationMethod"][0]["publicKeyMultibase"] = "z22XxPVrzTr24zUBGfZiZCm9bwj3RkHKLmx9ENYBxmY57o"
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}", True, True)
+
+ print("9. PASS: Registering DID Document with a verification method of type EcdsaSecp256k1RecoveryMethod2020. Only blockchainAccountId is passed.")
+ kp_algo = "recover-eth"
+ kp = generate_key_pair(algo=kp_algo)
+ did_doc_string = generate_did_document(kp, kp_algo)
+ did_doc_id = did_doc_string["id"]
+ signers = []
+ signPair = {
+ "kp": kp,
+ "verificationMethodId": did_doc_string["verificationMethod"][0]["id"],
+ "signing_algo": kp_algo
+ }
+ signers.append(signPair)
+ create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME)
+ run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}")
+
+ print("--- Test Completed ---\n")
+
\ No newline at end of file
diff --git a/tests/e2e/ssi_tests/generate_doc.py b/tests/e2e/ssi_tests/generate_doc.py
index 34c1905..a1aea56 100644
--- a/tests/e2e/ssi_tests/generate_doc.py
+++ b/tests/e2e/ssi_tests/generate_doc.py
@@ -3,7 +3,8 @@
sys.path.insert(1, os.getcwd())
import json
-from utils import run_command, generate_document_id, get_document_signature
+from utils import run_command, generate_document_id, get_document_signature, \
+ secp256k1_pubkey_to_address
def generate_did_document(key_pair, algo="ed25519"):
base_document = {
@@ -38,6 +39,11 @@ def generate_did_document(key_pair, algo="ed25519"):
}
if algo == "recover-eth":
verification_method["blockchainAccountId"] = "eip155:1:" + key_pair["ethereum_address"]
+ elif algo == "secp256k1":
+
+ verification_method["blockchainAccountId"] = "cosmos:jagrat:" + \
+ secp256k1_pubkey_to_address(key_pair["pub_key_base_64"], "hid")
+ verification_method["publicKeyMultibase"] = key_pair["pub_key_multibase"]
else:
verification_method["publicKeyMultibase"] = key_pair["pub_key_multibase"]
diff --git a/tests/e2e/ssi_tests/run.py b/tests/e2e/ssi_tests/run.py
index ac12599..75d5607 100644
--- a/tests/e2e/ssi_tests/run.py
+++ b/tests/e2e/ssi_tests/run.py
@@ -29,6 +29,9 @@ def run_all_tests():
schema_test()
deactivate_did()
credential_status_test()
+ caip10_ethereum_support_test()
+ caip10_cosmos_support_test()
+ vm_type_test()
print("============= 😃️ All test cases completed successfully ============== \n")
diff --git a/tests/e2e/ssi_tests/utils.py b/tests/e2e/ssi_tests/utils.py
index be1f6ba..3997f97 100644
--- a/tests/e2e/ssi_tests/utils.py
+++ b/tests/e2e/ssi_tests/utils.py
@@ -102,4 +102,9 @@ def get_document_signature(doc: dict, doc_type: str, key_pair: dict, algo: str =
cmd_string = f"hid-noded debug sign-ssi-doc {doc_cmd} '{json.dumps(doc)}' {private_key} {algo}"
signature, _ = run_command(cmd_string)
- return signature
\ No newline at end of file
+ return signature
+
+def secp256k1_pubkey_to_address(pub_key, prefix):
+ cmd_string = f"hid-noded debug secp256k1 bech32-addr {pub_key} {prefix}"
+ addr, _ = run_command(cmd_string)
+ return addr
diff --git a/x/ssi/types/common.go b/x/ssi/types/common.go
index f5f53e0..ad55034 100644
--- a/x/ssi/types/common.go
+++ b/x/ssi/types/common.go
@@ -12,6 +12,16 @@ var VerificationKeySignatureMap = map[string]string{
EcdsaSecp256k1RecoveryMethod2020: "EcdsaSecp256k1RecoverySignature2020",
}
+var supportedVerificationMethodTypes []string = func() []string {
+ result := []string{}
+
+ for vmType := range VerificationKeySignatureMap {
+ result = append(result, vmType)
+ }
+
+ return result
+}()
+
// Supported Service Types
var SupportedServiceTypes = []string{
"LinkedDomains",
@@ -21,14 +31,42 @@ var SupportedServiceTypes = []string{
const DocumentIdentifierDid = "did"
const DidMethod = "hid"
+// CAIP-10 prefixes
+const EthereumCAIP10Prefix string = "eip155" // Ethereum Based Chains
+const CosmosCAIP10Prefix string = "cosmos" // Cosmos Based Chains
+
// Supported CAIP-10 prefixes
-const EIP155 string = "eip155"
+var CAIP10PrefixForEcdsaSecp256k1RecoveryMethod2020 []string = []string{
+ EthereumCAIP10Prefix,
+}
+
+var CAIP10PrefixForEcdsaSecp256k1VerificationKey2019 []string = []string{
+ CosmosCAIP10Prefix,
+}
-// Support Client Specs
const ADR036Spec string = "cosmos-ADR036"
const PersonalSignSpec string = "eth-personalSign"
+// Supported Client Specs
var SupportedClientSpecs []string = []string{
ADR036Spec,
PersonalSignSpec,
}
+
+// Map between supported cosmos chain-id and their respective blockhchain address prefix
+var CosmosCAIP10ChainIdBech32PrefixMap = map[string]string{
+ // Mainnet Chains
+ "cosmoshub-4": "cosmos",
+ "osmosis-1": "osmo",
+ "akashnet-2": "akash",
+ "stargaze-1": "stars",
+ "core-1": "persistence",
+ "crypto-org-chain-mainnet-1": "cro",
+
+ // Testnet Chains
+ "theta-testnet-001": "cosmos",
+ "osmo-test-4": "osmo",
+ "elgafar-1": "stars",
+ "test-core-1": "persistence",
+ "jagrat": "hid",
+}
diff --git a/x/ssi/types/diddoc_validation.go b/x/ssi/types/diddoc_validation.go
index a6dba62..3f1aee7 100644
--- a/x/ssi/types/diddoc_validation.go
+++ b/x/ssi/types/diddoc_validation.go
@@ -76,31 +76,27 @@ func isDidUrl(id string) error {
}
func isSupportedVmType(typ string) error {
- supportedList := func() string {
- var resultStr string = ""
- for vKeyType := range VerificationKeySignatureMap {
- resultStr += fmt.Sprintf("%s, ", vKeyType)
- }
- return resultStr
- }
-
if _, supported := VerificationKeySignatureMap[typ]; !supported {
return fmt.Errorf(
- "%s verification method type not supported, supported verification method types: %s ",
+ "%v verification method type not supported, supported verification method types: %v ",
typ,
- supportedList(),
+ supportedVerificationMethodTypes,
)
}
return nil
}
// verificationKeyCheck validates of publicKeyMultibase and blockchainAccountId.
-// If the verfication method type is EcdsaSecp256k1RecoveryMethod2020, only blockchainAccountId
-// field must be populated, else only publicKeyMultibase must be populated.
func verificationKeyCheck(vm *VerificationMethod) error {
- // Verification Method of Type EcdsaSecp256k1RecoveryMethod2020 should only have
- // `blockchainAccountId` field populated
switch vm.Type {
+ case EcdsaSecp256k1VerificationKey2019:
+ if vm.GetPublicKeyMultibase() == "" {
+ return fmt.Errorf(
+ "publicKeyMultibase cannot be empty for verification method %s as it is type %s",
+ vm.Id,
+ vm.Type,
+ )
+ }
case EcdsaSecp256k1RecoveryMethod2020:
if vm.GetBlockchainAccountId() == "" {
return fmt.Errorf(
@@ -109,29 +105,31 @@ func verificationKeyCheck(vm *VerificationMethod) error {
vm.Type,
)
}
+
if vm.GetPublicKeyMultibase() != "" {
return fmt.Errorf(
- "publicKeyMultibase should be empty for verification method %s as it is type %s",
+ "publicKeyMultibase should not be provided for verification method %s as it is of type %s",
vm.Id,
vm.Type,
)
}
-
- default:
+ case Ed25519VerificationKey2020:
if vm.GetBlockchainAccountId() != "" {
return fmt.Errorf(
- "blockchainAccountId should be empty for verification method %s as it is of type %s",
+ "blockchainAccountId is currently not supported for verification method %s as it is of type %s",
vm.Id,
vm.Type,
)
}
if vm.GetPublicKeyMultibase() == "" {
return fmt.Errorf(
- "publicKeyMultibase cannot be empty for verification method %s as it is type %s",
+ "publicKeyMultibase cannot be empty for verification method %s as it is of type %s",
vm.Id,
vm.Type,
)
}
+ default:
+ return fmt.Errorf("unsupported verification method type: %v", supportedVerificationMethodTypes)
}
return nil
diff --git a/x/ssi/verification/address.go b/x/ssi/verification/address.go
new file mode 100644
index 0000000..cdcf49b
--- /dev/null
+++ b/x/ssi/verification/address.go
@@ -0,0 +1,31 @@
+package verification
+
+import (
+ "crypto/sha256"
+ "fmt"
+
+ "golang.org/x/crypto/ripemd160" // nolint: staticcheck
+
+ bech32 "github.com/cosmos/cosmos-sdk/types/bech32"
+)
+
+// publicKeyToCosmosBech32Address converts publicKey byteArray to Bech32 encoded blockchain address
+func publicKeyToCosmosBech32Address(addressPrefix string, pubKeyBytes []byte) string {
+ // Throw error if the length of secp256k1 publicKey is not 33
+ if len(pubKeyBytes) != 33 {
+ panic(fmt.Sprintf("invalid secp256k1 public key length %v", len(pubKeyBytes)))
+ }
+
+ // Hash pubKeyBytes as: RIPEMD160(SHA256(public_key_bytes))
+ pubKeySha256Hash := sha256.Sum256(pubKeyBytes)
+ ripemd160hash := ripemd160.New()
+ ripemd160hash.Write(pubKeySha256Hash[:])
+ addressBytes := ripemd160hash.Sum(nil)
+
+ // Convert addressBytes to bech32 encoded address
+ address, err := bech32.ConvertAndEncode(addressPrefix, addressBytes)
+ if err != nil {
+ panic(err)
+ }
+ return address
+}
diff --git a/x/ssi/verification/caip10.go b/x/ssi/verification/caip10.go
index 7cfc193..54bb476 100644
--- a/x/ssi/verification/caip10.go
+++ b/x/ssi/verification/caip10.go
@@ -1,6 +1,7 @@
package verification
import (
+ "fmt"
"strings"
"github.com/hypersign-protocol/hid-node/x/ssi/types"
@@ -13,15 +14,37 @@ func getBlockchainAddress(blockchainAccountId string) string {
return blockchainAddress
}
+// getChainIdFromBlockchainAccountId extracts chain from blockchainAccountId
+func getChainIdFromBlockchainAccountId(blockchainAccountId string) string {
+ chainIdIdx := 1
+
+ blockchainAccountIdElements := strings.Split(blockchainAccountId, ":")
+ blockchainChainId := blockchainAccountIdElements[chainIdIdx]
+ return blockchainChainId
+}
+
// Extracts the CAIP-10 prefix from blockchainAccountId and returns the chain spec
-func getCAIP10Chain(blockchainAccountId string) string {
+func getCAIP10Prefix(blockchainAccountId string) (string, error) {
segments := strings.Split(blockchainAccountId, ":")
- userPrefix := strings.Join(segments[0:len(segments)-1], ":")
- // Ethereum based chain (EIP-155) check
- if strings.HasPrefix(userPrefix, types.EIP155) {
- return types.EIP155
+ // Validate blockchainAccountId
+ if len(segments) != 3 {
+ return "", fmt.Errorf(
+ "invalid CAIP-10 format for blockchainAccountId '%v'. Please refer: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md",
+ blockchainAccountId,
+ )
+ }
+
+ // Prefix check
+ if segments[0] == types.EthereumCAIP10Prefix {
+ return types.EthereumCAIP10Prefix, nil
+ } else if segments[0] == types.CosmosCAIP10Prefix {
+ return types.CosmosCAIP10Prefix, nil
} else {
- return ""
+ return "", fmt.Errorf(
+ "unsupported CAIP-10 prefix in blockchainAccountId '%v'. Supported CAIP-10 prefixes: %v",
+ blockchainAccountId,
+ types.CAIP10PrefixForEcdsaSecp256k1RecoveryMethod2020,
+ )
}
}
diff --git a/x/ssi/verification/crypto.go b/x/ssi/verification/crypto.go
index 385d770..0231063 100644
--- a/x/ssi/verification/crypto.go
+++ b/x/ssi/verification/crypto.go
@@ -41,92 +41,173 @@ func verifyAny(extendedVmList []*types.ExtendedVerificationMethod, data []byte)
}
func verify(extendedVm *types.ExtendedVerificationMethod, data []byte) error {
- var verificationKey string
- var signature string = extendedVm.Signature
-
- if extendedVm.PublicKeyMultibase != "" {
- verificationKey = extendedVm.PublicKeyMultibase
- } else if extendedVm.BlockchainAccountId != "" {
- verificationKey = extendedVm.BlockchainAccountId
- } else {
- return fmt.Errorf("either publicKeyMultibase or BlockchainAccountId must be present")
- }
-
switch extendedVm.Type {
case types.Ed25519VerificationKey2020:
- return verifyEd25519(verificationKey, signature, data)
+ return verifyEd25519VerificationKey2020Key(extendedVm, data)
case types.EcdsaSecp256k1VerificationKey2019:
- return verifySecp256k1(verificationKey, signature, data)
+ return verifyEcdsaSecp256k1VerificationKey2019Key(extendedVm, data)
case types.EcdsaSecp256k1RecoveryMethod2020:
- chain := getCAIP10Chain(verificationKey)
- // Check for supported chains
- switch chain {
- // Ethereum based chains
- case types.EIP155:
- return recoverEthPublicKey(verificationKey, signature, data)
- default:
- return fmt.Errorf("unsupported blockchain address: %s", verificationKey)
- }
+ return verifyEcdsaSecp256k1RecoveryMethod2020Key(extendedVm, data)
default:
return fmt.Errorf("unsupported verification method: %s", extendedVm.Type)
}
}
-func verifyEd25519(publicKey string, signature string, documentBytes []byte) error {
+// verifyEcdsaSecp256k1RecoveryMethod2020Key verifies the verification key for verification method type EcdsaSecp256k1RecoveryMethod2020
+func verifyEcdsaSecp256k1RecoveryMethod2020Key(extendedVm *types.ExtendedVerificationMethod, documentBytes []byte) error {
+ extractedCAIP10Prefix, err := getCAIP10Prefix(extendedVm.BlockchainAccountId)
+ if err != nil {
+ return err
+ }
+
+ switch extractedCAIP10Prefix {
+ case types.EthereumCAIP10Prefix:
+ return verifyEthereumBlockchainAccountId(
+ extendedVm,
+ documentBytes,
+ )
+ default:
+ return fmt.Errorf(
+ "unsupported CAIP-10 prefix: '%v', supported CAIP-10 prefixes for verification method type %v: %v",
+ extractedCAIP10Prefix,
+ extendedVm.Type,
+ types.CAIP10PrefixForEcdsaSecp256k1RecoveryMethod2020,
+ )
+ }
+}
+
+// verifyEd25519VerificationKey2020Key verifies the verification key for verification method type Ed25519VerificationKey2020
+func verifyEd25519VerificationKey2020Key(extendedVm *types.ExtendedVerificationMethod, documentBytes []byte) error {
// Decode Public Key
- _, publicKeyBytes, err := multibase.Decode(publicKey)
+ _, publicKeyBytes, err := multibase.Decode(extendedVm.PublicKeyMultibase)
if err != nil {
return fmt.Errorf(
"cannot decode Ed25519 public key %s",
- publicKey,
+ extendedVm.PublicKeyMultibase,
)
}
// Decode Signatures
- signatureBytes, err := base64.StdEncoding.DecodeString(signature)
+ signatureBytes, err := base64.StdEncoding.DecodeString(extendedVm.Signature)
if err != nil {
return err
}
if !ed25519.Verify(publicKeyBytes, documentBytes, signatureBytes) {
- return fmt.Errorf("ed25519: signature could not be verified")
+ return fmt.Errorf("signature could not be verified for verificationMethodId: %v", extendedVm.Id)
} else {
return nil
}
}
-func verifySecp256k1(publicKey string, signature string, documentBytes []byte) error {
+// verifyEcdsaSecp256k1VerificationKey2019Key verifies the verification key for verification method type EcdsaSecp256k1VerificationKey2019
+func verifyEcdsaSecp256k1VerificationKey2019Key(extendedVm *types.ExtendedVerificationMethod, documentBytes []byte) error {
+ // Decode and Parse Signature
+ signatureBytes, err := base64.StdEncoding.DecodeString(extendedVm.Signature)
+ if err != nil {
+ return err
+ }
+
// Decode Public Key
- _, publicKeyBytes, err := multibase.Decode(publicKey)
+ _, publicKeyBytes, err := multibase.Decode(extendedVm.PublicKeyMultibase)
if err != nil {
return err
}
var pubKeyObj secp256k1.PubKey = publicKeyBytes
- // Decode and Parse Signature
- signatureBytes, err := base64.StdEncoding.DecodeString(signature)
+ // Check if the signature is valid for given publicKeyMultibase
+ if !pubKeyObj.VerifySignature(documentBytes, signatureBytes) {
+ return fmt.Errorf("signature could not be verified for verificationMethodId: %v", extendedVm.Id)
+ }
+
+ // Check if blockchainAccountId is passed
+ if extendedVm.BlockchainAccountId != "" {
+ extractedCAIP10Prefix, err := getCAIP10Prefix(extendedVm.BlockchainAccountId)
+ if err != nil {
+ return err
+ }
+
+ switch extractedCAIP10Prefix {
+ case types.CosmosCAIP10Prefix:
+ return verifyCosmosBlockchainAccountId(
+ extendedVm.BlockchainAccountId,
+ extendedVm.PublicKeyMultibase,
+ )
+ default:
+ return fmt.Errorf(
+ "unsupported CAIP-10 prefix: '%v', supported CAIP-10 prefixes for verification method type %v: %v",
+ extractedCAIP10Prefix,
+ extendedVm.Type,
+ types.CAIP10PrefixForEcdsaSecp256k1VerificationKey2019,
+ )
+ }
+ }
+
+ return nil
+}
+
+// verifyCosmosBlockchainAccountId verifies Cosmos Ecosystem based blockchain address. The verified
+// publicKeyMultibase is converted to a bech32 encoded blockchain address which is then compared with the
+// user provided blockchain address. If they do not match, error is returned.
+func verifyCosmosBlockchainAccountId(blockchainAccountId, publicKeyMultibase string) error {
+ // Check if the blockchainAccountId prefix is valid
+ extractedCAIP10Prefix, err := getCAIP10Prefix(blockchainAccountId)
if err != nil {
return err
}
+ if extractedCAIP10Prefix != types.CosmosCAIP10Prefix {
+ return fmt.Errorf(
+ "expected CAIP-10 prefix to be '%v', got '%v'",
+ types.CosmosCAIP10Prefix,
+ extractedCAIP10Prefix,
+ )
+ }
- if !pubKeyObj.VerifySignature(documentBytes, signatureBytes) {
- return fmt.Errorf("secp256k1: signature could not be verified")
+ // Decode public key
+ _, publicKeyBytes, err := multibase.Decode(publicKeyMultibase)
+ if err != nil {
+ return err
+ }
+
+ // Convert publicKeyMultibase to bech32 encoded blockchain address
+ chainId := getChainIdFromBlockchainAccountId(blockchainAccountId)
+ addressPrefix, isChainSupported := types.CosmosCAIP10ChainIdBech32PrefixMap[chainId]
+ if !isChainSupported {
+ return fmt.Errorf(
+ "cosmos chain with chain-id '%v' in blockchainAccountId '%v' is not supported",
+ chainId,
+ blockchainAccountId,
+ )
+ }
+ convertedAddress := publicKeyToCosmosBech32Address(
+ addressPrefix,
+ publicKeyBytes,
+ )
+
+ // Compare converted blockchain address
+ if convertedAddress != getBlockchainAddress(blockchainAccountId) {
+ return fmt.Errorf(
+ "blockchain address provided in blockchainAccountId '%v' is unexpected",
+ blockchainAccountId,
+ )
} else {
return nil
}
}
-// Support for EIP155 based blockchain address
-func recoverEthPublicKey(blockchainAccountId string, signature string, documentBytes []byte) error {
+// verifyEthereumBlockchainAccountId verifies Ethereum Ecosystem based blockchain address. A secp256k1 based
+// publicKey is extracted from the recoverable Secp256k1 signature. It is converted into a hex encoded based
+// blockchain address, and matched with user provided blockchain address. If they do not match, error is returned.
+func verifyEthereumBlockchainAccountId(extendedVm *types.ExtendedVerificationMethod, documentBytes []byte) error {
// Extract blockchain address from blockchain account id
- blockchainAddress := getBlockchainAddress(blockchainAccountId)
+ blockchainAddress := getBlockchainAddress(extendedVm.BlockchainAccountId)
// Convert message bytes to hash
// More info on the `personal_sign` here: https://docs.metamask.io/guide/signing-data.html#personal-sign
msgHash := etheraccounts.TextHash(documentBytes)
// Decode hex-encoded signature string to bytes
- signatureBytes, err := etherhexutil.Decode(signature)
+ signatureBytes, err := etherhexutil.Decode(extendedVm.Signature)
if err != nil {
return err
}
@@ -142,7 +223,7 @@ func recoverEthPublicKey(blockchainAccountId string, signature string, documentB
return err
}
- // Convert public key to hex-encoded address
+ // Convert public key to b-encoded address
recoveredBlockchainAddress := ethercrypto.PubkeyToAddress(*recoveredPublicKey).Hex()
// Match the recovered address against user provided address
diff --git a/x/ssi/verification/signature_verification.go b/x/ssi/verification/signature_verification.go
index 5d43a49..0ce83c6 100644
--- a/x/ssi/verification/signature_verification.go
+++ b/x/ssi/verification/signature_verification.go
@@ -16,7 +16,7 @@ func VerifySignatureOfEveryController(
}
err := verifyAll(vmList, didDoc)
if err != nil {
- return fmt.Errorf("need every signature for controller %s to be valid", controller)
+ return fmt.Errorf("%s: need every signature for controller %s to be valid", err.Error(), controller)
}
}
return nil