forked from trent_larson/crowd-funder-for-time-pwa
fix: consolidate deep link testing scripts
- Remove redundant test-deeplinks.sh script - Update run-deeplink-tests.sh with improved timeout - Remove unused secp256k1-sign script - Remove deprecated new_flow scripts - Clean up test script directory Technical Changes: - Increase default timeout from 5 to 10 seconds - Remove Python/TypeScript DID generation scripts - Remove redundant deep link test script - Remove unused crypto signing script This simplifies the test script directory by removing deprecated scripts and consolidating deep link testing into a single, more reliable script.
This commit is contained in:
@@ -22,12 +22,14 @@ The `run-deeplink-tests.sh` script tests the app's deep link handling capabiliti
|
||||
1. Generate required test files using either:
|
||||
|
||||
Python method:
|
||||
|
||||
```bash
|
||||
pip install mnemonic eth_account eth_keys web3 requests
|
||||
python test-scripts/new_flow.py
|
||||
python test-scripts/generate_data.py
|
||||
```
|
||||
|
||||
OR TypeScript method:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
@@ -42,16 +44,19 @@ The `run-deeplink-tests.sh` script tests the app's deep link handling capabiliti
|
||||
### Running Tests
|
||||
|
||||
1. Execute tests:
|
||||
|
||||
```bash
|
||||
./test-scripts/run-deeplink-tests.sh
|
||||
```
|
||||
|
||||
2. Print mode (no device needed):
|
||||
|
||||
```bash
|
||||
./test-scripts/run-deeplink-tests.sh -p
|
||||
```
|
||||
|
||||
3. Custom timeout:
|
||||
|
||||
```bash
|
||||
./test-scripts/run-deeplink-tests.sh -t 10
|
||||
```
|
||||
@@ -59,6 +64,7 @@ The `run-deeplink-tests.sh` script tests the app's deep link handling capabiliti
|
||||
### Troubleshooting
|
||||
|
||||
If you encounter errors:
|
||||
|
||||
1. Ensure `.generated` directory exists with required files
|
||||
2. Check ADB is installed for device testing
|
||||
3. Run with `-p` flag to verify deep link generation
|
||||
@@ -67,7 +73,7 @@ If you encounter errors:
|
||||
### Clean Up
|
||||
|
||||
To clean up generated files:
|
||||
|
||||
```bash
|
||||
rm -rf .generated
|
||||
```
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Requirements: jq, python, and `pip install -r requirements.txt`
|
||||
# Usage: ./check-did.sh [did]
|
||||
# If no DID provided, lists all visible DIDs
|
||||
|
||||
API_URL=${ENDORSER_API_URL:-"https://test-api.endorser.ch/api"}
|
||||
ADMIN_DID="did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"
|
||||
ADMIN_KEY="2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b"
|
||||
|
||||
# Create verification JWT using Python (equivalent to uport-credentials)
|
||||
jwt=$(python3 -c "
|
||||
from eth_keys import keys
|
||||
import hashlib, base64, json, time
|
||||
|
||||
# Create header and payload
|
||||
header = {'typ': 'JWT', 'alg': 'ES256K'}
|
||||
payload = {
|
||||
'iss': '$ADMIN_DID',
|
||||
'exp': int(time.time()) + 300 # 5 minutes from now
|
||||
}
|
||||
|
||||
# Base64url encode header and payload
|
||||
def b64url(data):
|
||||
return base64.urlsafe_b64encode(json.dumps(data).encode()).decode().rstrip('=')
|
||||
|
||||
header_b64 = b64url(header)
|
||||
payload_b64 = b64url(payload)
|
||||
message = f'{header_b64}.{payload_b64}'
|
||||
|
||||
# Sign using admin key
|
||||
private_key = keys.PrivateKey(bytes.fromhex('$ADMIN_KEY'))
|
||||
message_hash = hashlib.sha256(message.encode()).digest()
|
||||
signature = private_key.sign_msg_hash(message_hash)
|
||||
signature_bytes = signature.r.to_bytes(32, 'big') + signature.s.to_bytes(32, 'big')
|
||||
signature_b64 = base64.urlsafe_b64encode(signature_bytes).decode().rstrip('=')
|
||||
|
||||
# Output complete JWT
|
||||
print(f'{message}.{signature_b64}')
|
||||
")
|
||||
|
||||
REQUEST_URL="$API_URL/report/whichDidsICanSee"
|
||||
echo "Making request to: $REQUEST_URL"
|
||||
echo "Getting visible DIDs..."
|
||||
response=$(curl -s -X GET "$REQUEST_URL" \
|
||||
-H "Authorization: Bearer $jwt" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
echo -e "\nResponse:"
|
||||
echo "$response" | jq '.'
|
||||
|
||||
# If specific DID provided, check if it's in the list
|
||||
if [ -n "$1" ]; then
|
||||
echo -e "\nChecking if DID $1 is visible..."
|
||||
if echo "$response" | jq -e --arg did "$1" '.[] | select(. == $did)' > /dev/null; then
|
||||
echo "✅ DID is registered and visible"
|
||||
else
|
||||
echo "❌ DID not found in visible list"
|
||||
fi
|
||||
fi
|
||||
@@ -1,364 +0,0 @@
|
||||
"""
|
||||
DID Generator Script
|
||||
@author Matthew Raymer
|
||||
|
||||
This script generates and registers Decentralized Identifiers (DIDs) with admin authorization.
|
||||
It supports the creation of Ethereum-based DIDs (did:ethr) and handles the complete
|
||||
registration flow with the endorser.ch API.
|
||||
|
||||
Features:
|
||||
- Ethereum keypair generation with compressed public keys
|
||||
- JWT creation and signing using ES256K
|
||||
- DID registration with admin authorization
|
||||
- Detailed error handling and logging
|
||||
- Command-line interface for admin DID input
|
||||
|
||||
Dependencies:
|
||||
eth_account: For Ethereum account operations
|
||||
eth_keys: For key manipulation and compression
|
||||
requests: For API communication
|
||||
secrets: For secure random number generation
|
||||
hashlib: For SHA-256 hashing
|
||||
base64: For JWT encoding
|
||||
argparse: For command-line argument parsing
|
||||
pathlib: For path handling
|
||||
dotenv: For environment variable loading
|
||||
os: For environment variable access
|
||||
|
||||
Usage:
|
||||
python did_generator.py [options]
|
||||
|
||||
Options:
|
||||
--admin-did <did> Override default admin DID
|
||||
--api-url <url> Override default API endpoint
|
||||
|
||||
Environment:
|
||||
Default Admin DID: did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F
|
||||
Default API URL: https://test-api.endorser.ch/api/v2/claim
|
||||
"""
|
||||
|
||||
from eth_account import Account
|
||||
import json
|
||||
import base64
|
||||
from eth_account.messages import encode_defunct
|
||||
from eth_keys import keys
|
||||
import time
|
||||
import requests
|
||||
import argparse
|
||||
import secrets
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
class DIDRegistration:
|
||||
"""
|
||||
Handles the creation and registration of DIDs with admin authorization.
|
||||
|
||||
This class manages the complete lifecycle of DID creation and registration:
|
||||
1. Generating secure Ethereum keypairs with compressed public keys
|
||||
2. Creating DIDs from public keys in did:ethr format
|
||||
3. Signing registration claims with admin credentials
|
||||
4. Submitting registration to the endorser.ch API
|
||||
|
||||
The registration process uses two keypairs:
|
||||
1. Admin keypair: Used to sign and authorize the registration
|
||||
2. New DID keypair: The identity being registered
|
||||
|
||||
Attributes:
|
||||
api_url (str): Endpoint for DID registration
|
||||
admin_keypair (dict): Admin's credentials containing:
|
||||
- did: Admin's DID in did:ethr format
|
||||
- private_key: Admin's private key for signing
|
||||
"""
|
||||
|
||||
def __init__(self, admin_keypair: dict, api_url: str = None):
|
||||
"""
|
||||
Initialize DID registration with admin credentials.
|
||||
|
||||
Args:
|
||||
admin_keypair (dict): Admin's DID and private key for signing
|
||||
api_url (str, optional): Override default API URL
|
||||
"""
|
||||
self.api_url = api_url or "https://test-api.endorser.ch/api/v2/claim"
|
||||
self.admin_keypair = admin_keypair # Store full admin keypair
|
||||
Account.enable_unaudited_hdwallet_features()
|
||||
|
||||
def create_keypair(self) -> dict:
|
||||
"""
|
||||
Generate a new Ethereum keypair and associated DID.
|
||||
|
||||
Creates a secure random keypair and formats it for use with the
|
||||
endorser.ch API. Uses compressed public key format to match
|
||||
ethers.js implementation.
|
||||
|
||||
Returns:
|
||||
dict: Keypair information containing:
|
||||
- private_key: Raw private key without 0x prefix
|
||||
- public_key: Compressed public key with 0x prefix
|
||||
- address: Ethereum address
|
||||
- did: Generated DID in did:ethr format
|
||||
|
||||
Security:
|
||||
- Uses secrets module for cryptographically secure randomness
|
||||
- Implements compressed public key format
|
||||
- Maintains private key security
|
||||
"""
|
||||
private_key = secrets.token_hex(32)
|
||||
|
||||
# Create private key object and derive public key
|
||||
private_key_bytes = bytes.fromhex(private_key)
|
||||
private_key_obj = keys.PrivateKey(private_key_bytes)
|
||||
|
||||
# Get compressed public key (like ethers.js)
|
||||
public_key_obj = private_key_obj.public_key
|
||||
public_key = '0x' + public_key_obj.to_compressed_bytes().hex()
|
||||
|
||||
# Create account from private key (for address)
|
||||
account = Account.from_key(private_key_bytes)
|
||||
|
||||
return {
|
||||
'private_key': private_key, # No 0x prefix
|
||||
'public_key': public_key, # With 0x prefix, compressed format
|
||||
'address': account.address,
|
||||
'did': f"did:ethr:{account.address}"
|
||||
}
|
||||
|
||||
def sign_jwt(self, payload: dict, private_key: str, did: str) -> str:
|
||||
"""
|
||||
Sign a JWT using ES256K algorithm.
|
||||
|
||||
Creates and signs a JWT following the did-jwt specification:
|
||||
1. Constructs header and payload
|
||||
2. Base64url encodes components
|
||||
3. Signs using ES256K
|
||||
4. Assembles final JWT
|
||||
|
||||
Args:
|
||||
payload (dict): JWT payload to sign
|
||||
private_key (str): Private key for signing (without 0x prefix)
|
||||
did (str): DID to use as issuer
|
||||
|
||||
Returns:
|
||||
str: Signed JWT string in format: header.payload.signature
|
||||
|
||||
Security:
|
||||
- Implements ES256K signing
|
||||
- Follows did-jwt specification
|
||||
- Handles message hashing correctly
|
||||
"""
|
||||
# Add issuer to payload like did-jwt does
|
||||
full_payload = {
|
||||
**payload,
|
||||
"iss": did
|
||||
}
|
||||
|
||||
header = {
|
||||
"typ": "JWT",
|
||||
"alg": "ES256K"
|
||||
}
|
||||
|
||||
# Create the JWT segments
|
||||
header_b64 = base64.urlsafe_b64encode(
|
||||
json.dumps(header, separators=(',', ':')).encode()
|
||||
).decode().rstrip('=')
|
||||
payload_b64 = base64.urlsafe_b64encode(
|
||||
json.dumps(full_payload, separators=(',', ':')).encode()
|
||||
).decode().rstrip('=')
|
||||
message = f"{header_b64}.{payload_b64}"
|
||||
|
||||
# Hash the message with sha256
|
||||
message_hash = hashlib.sha256(message.encode()).digest()
|
||||
|
||||
# Sign using eth_keys directly
|
||||
private_key_bytes = bytes.fromhex(private_key)
|
||||
private_key_obj = keys.PrivateKey(private_key_bytes)
|
||||
signature = private_key_obj.sign_msg_hash(message_hash)
|
||||
|
||||
# Get r and s from signature
|
||||
r = signature.r.to_bytes(32, 'big')
|
||||
s = signature.s.to_bytes(32, 'big')
|
||||
signature_bytes = r + s
|
||||
|
||||
# Format signature
|
||||
signature_b64 = base64.urlsafe_b64encode(signature_bytes).decode().rstrip('=')
|
||||
|
||||
return f"{message}.{signature_b64}"
|
||||
|
||||
def create_jwt(self, new_did: str) -> str:
|
||||
"""
|
||||
Create a signed JWT for DID registration
|
||||
|
||||
Args:
|
||||
new_did (str): The DID being registered
|
||||
"""
|
||||
now = int(time.time())
|
||||
|
||||
# Create registration claim with admin as agent
|
||||
register_claim = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "RegisterAction",
|
||||
"agent": { "did": self.admin_keypair['did'] },
|
||||
"participant": { "did": new_did },
|
||||
"object": "endorser.ch"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"iat": now,
|
||||
"exp": now + 300,
|
||||
"sub": "RegisterAction",
|
||||
"vc": {
|
||||
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
||||
"type": ["VerifiableCredential"],
|
||||
"credentialSubject": register_claim
|
||||
}
|
||||
}
|
||||
|
||||
print(f"\nDebug - JWT payload: {json.dumps(payload, indent=2)}")
|
||||
|
||||
# Sign with admin's private key
|
||||
return self.sign_jwt(
|
||||
payload,
|
||||
self.admin_keypair['private_key'],
|
||||
self.admin_keypair['did']
|
||||
)
|
||||
|
||||
def register_did(self, jwt_token: str) -> dict:
|
||||
"""
|
||||
Submit DID registration to the endorser.ch API.
|
||||
|
||||
Handles the complete registration process:
|
||||
1. Submits JWT to API
|
||||
2. Processes response
|
||||
3. Formats result or error message
|
||||
|
||||
Args:
|
||||
jwt_token (str): Signed JWT for registration
|
||||
|
||||
Returns:
|
||||
dict: Registration result containing:
|
||||
- success: Boolean indicating success
|
||||
- response: API response data
|
||||
- error: Error message if failed
|
||||
|
||||
Security:
|
||||
- Uses HTTPS for API communication
|
||||
- Validates response status
|
||||
- Handles errors gracefully
|
||||
"""
|
||||
try:
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json={"jwtEncoded": jwt_token},
|
||||
headers={'Content-Type': 'application/json'}
|
||||
)
|
||||
|
||||
print(f"\nServer Response Status: {response.status_code}")
|
||||
print(f"Server Response Body: {response.text}")
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
return {
|
||||
'success': True,
|
||||
'response': response.json()
|
||||
}
|
||||
else:
|
||||
try:
|
||||
error_json = response.json()
|
||||
error_msg = error_json.get('error', {}).get(
|
||||
'message', 'Unknown error'
|
||||
)
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Registration failed ({response.status_code}): "
|
||||
f"{error_msg}",
|
||||
'response': error_json
|
||||
}
|
||||
except json.JSONDecodeError:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Registration failed ({response.status_code}): "
|
||||
f"{response.text}",
|
||||
'response': response.text
|
||||
}
|
||||
except (requests.RequestException, ConnectionError) as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Request failed: {str(e)}"
|
||||
}
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for DID generation script.
|
||||
|
||||
Handles:
|
||||
1. Command line argument parsing
|
||||
2. DID generation and registration process
|
||||
3. Result output and error display
|
||||
|
||||
Usage:
|
||||
python did_generator.py [options]
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate a DID with admin authorization'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--admin-did',
|
||||
help='Admin DID (e.g., did:ethr:0x0000...)',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--api-url',
|
||||
help='Override API URL',
|
||||
default=os.getenv('ENDORSER_API_URL', 'https://test-api.endorser.ch/api/v2/claim')
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get admin credentials from environment
|
||||
admin_keypair = {
|
||||
'did': os.getenv('ADMIN_DID', 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F'),
|
||||
'private_key': os.getenv('ADMIN_PRIVATE_KEY', '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b')
|
||||
}
|
||||
|
||||
admin_did = args.admin_did
|
||||
if not admin_did:
|
||||
admin_did = admin_keypair['did']
|
||||
|
||||
print(f"Admin DID: {admin_did}")
|
||||
print(f"API URL: {args.api_url}")
|
||||
|
||||
print('Starting DID Generation...\n')
|
||||
registrar = DIDRegistration(admin_keypair, args.api_url)
|
||||
|
||||
print("Generating new keypair...")
|
||||
new_keypair = registrar.create_keypair()
|
||||
|
||||
print("\nGenerated DID Details:")
|
||||
print("----------------------")
|
||||
print(f"DID: {new_keypair['did']}")
|
||||
print(f"Admin DID: {admin_did}")
|
||||
print(f"Address: {new_keypair['address']}")
|
||||
print(f"Private Key: {new_keypair['private_key']}")
|
||||
print(f"Public Key: {new_keypair['public_key']}\n")
|
||||
|
||||
print("Creating JWT...")
|
||||
jwt_token = registrar.create_jwt(new_keypair['did'])
|
||||
|
||||
print('\nSuccessfully generated DID with admin authorization!')
|
||||
print(f'Registration JWT: {jwt_token[:50]}...')
|
||||
|
||||
print("\nAttempting registration...")
|
||||
result = registrar.register_did(jwt_token)
|
||||
if result['success']:
|
||||
print("Registration successful!")
|
||||
print("Response: {json.dumps(result['response'], indent=2)}")
|
||||
else:
|
||||
print("Registration failed!")
|
||||
print(f"Error: {result['error']}")
|
||||
if 'response' in result:
|
||||
print(f"Full response: {json.dumps(result['response'], indent=2)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,266 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check for required commands
|
||||
for cmd in openssl xxd jq curl base64; do
|
||||
if ! command -v $cmd &> /dev/null; then
|
||||
echo "Error: $cmd is not installed. Please install it first."
|
||||
echo "On Arch Linux: sudo pacman -S $cmd"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Default values
|
||||
ADMIN_DID=${ADMIN_DID:-"did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"}
|
||||
ADMIN_PRIVATE_KEY=${ADMIN_PRIVATE_KEY:-"2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b"}
|
||||
API_URL=${ENDORSER_API_URL:-"https://test-api.endorser.ch/api/v2/claim"}
|
||||
DEBUG=${DEBUG:-0}
|
||||
|
||||
# Function to log debug info
|
||||
debug_log() {
|
||||
if [ "$DEBUG" = "1" ]; then
|
||||
echo "$@" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to generate a new keypair
|
||||
generate_keypair() {
|
||||
debug_log "Generating new keypair..."
|
||||
|
||||
# Create a temporary directory for key operations
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
# Generate private key
|
||||
openssl ecparam -name secp256k1 -genkey -noout > "$TMPDIR/private.pem"
|
||||
|
||||
# Extract raw private key
|
||||
PRIVATE_KEY=$(openssl ec -in "$TMPDIR/private.pem" -text -noout 2>/dev/null | \
|
||||
grep priv -A 3 | tail -n +2 | \
|
||||
tr -d '\n[:space:]:' | \
|
||||
sed 's/^00//')
|
||||
|
||||
# Generate public key (compressed format)
|
||||
PUBLIC_KEY=$(openssl ec -in "$TMPDIR/private.pem" -pubout -outform DER 2>/dev/null | \
|
||||
tail -c 65 | \
|
||||
xxd -p -c 65 | \
|
||||
sed 's/^04/02/')
|
||||
|
||||
# Generate Ethereum address
|
||||
ADDRESS=$(echo -n "$PUBLIC_KEY" | \
|
||||
xxd -r -p | \
|
||||
openssl dgst -sha3-256 -binary | \
|
||||
tail -c 20 | \
|
||||
xxd -p)
|
||||
|
||||
# Create DID
|
||||
DID="did:ethr:0x$ADDRESS"
|
||||
|
||||
# Print debug info if enabled
|
||||
debug_log "Generated DID Details:"
|
||||
debug_log "----------------------"
|
||||
debug_log "DID: $DID"
|
||||
debug_log "Address: $ADDRESS"
|
||||
debug_log "Private Key: $PRIVATE_KEY"
|
||||
debug_log "Public Key: $PUBLIC_KEY"
|
||||
|
||||
# Export variables for other functions
|
||||
export DID PRIVATE_KEY PUBLIC_KEY ADDRESS
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to hex_to_dec
|
||||
hex_to_dec() {
|
||||
printf "%d" "0x$1"
|
||||
}
|
||||
|
||||
# Function to attempt signing with retries
|
||||
attempt_signing() {
|
||||
local max_attempts=5
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
debug_log "Signing attempt $attempt of $max_attempts..."
|
||||
|
||||
# Sign message and get DER format signature
|
||||
openssl dgst -sha256 -sign "$TMPDIR/private.pem" "$TMPDIR/message.txt" 2>/dev/null > "$TMPDIR/signature.der"
|
||||
|
||||
# Convert DER signature to hex
|
||||
der_sig=$(xxd -p -c 256 "$TMPDIR/signature.der")
|
||||
|
||||
debug_log "Debug - Full DER signature:"
|
||||
debug_log "$der_sig"
|
||||
|
||||
# Parse DER structure with length checking
|
||||
if [[ $der_sig =~ ^30([0-9a-f]{2})02([0-9a-f]{2})([0-9a-f]*)02([0-9a-f]{2})([0-9a-f]*)$ ]]; then
|
||||
seq_len="${BASH_REMATCH[1]}"
|
||||
r_len="${BASH_REMATCH[2]}"
|
||||
r_val="${BASH_REMATCH[3]}"
|
||||
s_len="${BASH_REMATCH[4]}"
|
||||
s_val="${BASH_REMATCH[5]}"
|
||||
|
||||
# Convert lengths to decimal
|
||||
r_len_dec=$((16#$r_len))
|
||||
s_len_dec=$((16#$s_len))
|
||||
|
||||
debug_log "R length: $r_len_dec bytes"
|
||||
debug_log "S length: $s_len_dec bytes"
|
||||
|
||||
# Handle R value
|
||||
if [ $r_len_dec -gt 32 ]; then
|
||||
r_val=${r_val: -64} # Take last 32 bytes
|
||||
elif [ $r_len_dec -lt 32 ]; then
|
||||
r_val=$(printf "%064s" "$r_val" | tr ' ' '0') # Left pad
|
||||
fi
|
||||
|
||||
# Handle S value
|
||||
if [ $s_len_dec -gt 32 ]; then
|
||||
s_val=${s_val: -64} # Take last 32 bytes
|
||||
elif [ $s_len_dec -lt 32 ]; then
|
||||
s_val=$(printf "%064s" "$s_val" | tr ' ' '0') # Left pad
|
||||
fi
|
||||
|
||||
# Validate final lengths
|
||||
if [ ${#r_val} -eq 64 ] && [ ${#s_val} -eq 64 ]; then
|
||||
debug_log "Valid signature found on attempt $attempt"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
debug_log "Invalid signature on attempt $attempt, retrying..."
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
echo "Failed to generate valid signature after $max_attempts attempts" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to create and sign JWT
|
||||
create_jwt() {
|
||||
local now=$(date +%s)
|
||||
local exp=$((now + 300))
|
||||
|
||||
# Create header (compact JSON)
|
||||
local header='{"typ":"JWT","alg":"ES256K"}'
|
||||
|
||||
# Create registration claim (compact JSON)
|
||||
local claim="{\"iat\":$now,\"exp\":$exp,\"sub\":\"RegisterAction\",\"vc\":{\"@context\":[\"https://www.w3.org/2018/credentials/v1\"],\"type\":[\"VerifiableCredential\"],\"credentialSubject\":{\"@context\":\"https://schema.org\",\"@type\":\"RegisterAction\",\"agent\":{\"did\":\"$ADMIN_DID\"},\"participant\":{\"did\":\"$DID\"},\"object\":\"endorser.ch\"}},\"iss\":\"$ADMIN_DID\"}"
|
||||
|
||||
# Base64url encode header and claim
|
||||
header_b64=$(echo -n "$header" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
claim_b64=$(echo -n "$claim" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
|
||||
# Create message to sign
|
||||
message="$header_b64.$claim_b64"
|
||||
|
||||
# Create temporary directory for key operations
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
# Create private key PEM file
|
||||
echo "-----BEGIN EC PRIVATE KEY-----" > "$TMPDIR/private.pem"
|
||||
(
|
||||
echo "302e0201010420" # Private key header
|
||||
echo -n "$ADMIN_PRIVATE_KEY" # Private key bytes
|
||||
echo "a00706052b8104000a" # secp256k1 OID
|
||||
) | xxd -r -p | base64 >> "$TMPDIR/private.pem"
|
||||
echo "-----END EC PRIVATE KEY-----" >> "$TMPDIR/private.pem"
|
||||
|
||||
# Write message to file
|
||||
echo -n "$message" > "$TMPDIR/message.txt"
|
||||
|
||||
# Attempt signing with retries
|
||||
if attempt_signing; then
|
||||
# Create final JWT
|
||||
concat_sig="${r_val}${s_val}"
|
||||
signature=$(echo -n "$concat_sig" | xxd -r -p | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
JWT="$message.$signature"
|
||||
|
||||
debug_log "Created JWT: ${JWT:0:50}..."
|
||||
export JWT
|
||||
return 0
|
||||
else
|
||||
echo "Failed to generate valid signature" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to register DID
|
||||
register_did() {
|
||||
debug_log "Attempting registration..."
|
||||
|
||||
# Create request body
|
||||
body="{\"jwtEncoded\":\"$JWT\"}"
|
||||
|
||||
# Send registration request
|
||||
response=$(curl -s -X POST "$API_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$body")
|
||||
|
||||
# Check response
|
||||
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
debug_log "Registration successful!"
|
||||
debug_log "Response: $(echo "$response" | jq -c '.')"
|
||||
|
||||
# Output only the JSON result
|
||||
debug_log "About to output JSON..."
|
||||
jq -n \
|
||||
--arg did "$DID" \
|
||||
--arg private_key "$PRIVATE_KEY" \
|
||||
--arg registration_id "$(echo "$response" | jq -r '.success.registrationId')" \
|
||||
--arg claim_id "$(echo "$response" | jq -r '.success.claimId')" \
|
||||
'{
|
||||
status: "success",
|
||||
did: $did,
|
||||
privateKey: $private_key,
|
||||
registrationId: $registration_id,
|
||||
claimId: $claim_id
|
||||
}'
|
||||
debug_log "JSON output complete"
|
||||
exit 0
|
||||
else
|
||||
error_msg=$(echo "$response" | jq -r '.error.message // "Unknown error"')
|
||||
debug_log "Registration failed: $error_msg"
|
||||
debug_log "Full response: $(echo "$response" | jq -c '.' || echo "$response")"
|
||||
|
||||
# Output error as JSON
|
||||
jq -n \
|
||||
--arg error "$error_msg" \
|
||||
--arg stage "registration" \
|
||||
'{
|
||||
status: "error",
|
||||
stage: $stage,
|
||||
error: $error
|
||||
}'
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
debug_log "Starting DID Generation..."
|
||||
debug_log "Using admin DID: $ADMIN_DID"
|
||||
debug_log "API URL: $API_URL"
|
||||
|
||||
if ! generate_keypair; then
|
||||
jq -n '{
|
||||
status: "error",
|
||||
stage: "keypair",
|
||||
error: "Failed to generate keypair"
|
||||
}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! create_jwt; then
|
||||
jq -n '{
|
||||
status: "error",
|
||||
stage: "jwt",
|
||||
error: "Failed to generate valid signature"
|
||||
}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
register_did
|
||||
@@ -1,250 +0,0 @@
|
||||
/**
|
||||
* DID Generator Script
|
||||
* @author Matthew Raymer
|
||||
*
|
||||
* This script generates and registers Decentralized Identifiers (DIDs) with admin authorization.
|
||||
* It supports the creation of Ethereum-based DIDs (did:ethr) and handles the complete
|
||||
* registration flow with the endorser.ch API.
|
||||
*
|
||||
* Features:
|
||||
* - Ethereum keypair generation using ethers.js
|
||||
* - JWT creation and signing using did-jwt
|
||||
* - DID registration with admin authorization
|
||||
* - Detailed error handling and logging
|
||||
* - Command-line interface for admin DID input
|
||||
*
|
||||
* Dependencies:
|
||||
* did-jwt: For JWT creation and signing
|
||||
* ethers: For Ethereum account operations
|
||||
* node-fetch: For API communication
|
||||
* commander: For CLI argument parsing
|
||||
* dotenv: For environment variable loading
|
||||
*
|
||||
* Usage:
|
||||
* npm run generate-did -- [options]
|
||||
*
|
||||
* Options:
|
||||
* -a, --admin-did <did> Override default admin DID
|
||||
* --api-url <url> Override default API endpoint
|
||||
*
|
||||
* Environment:
|
||||
* Default Admin DID: did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F
|
||||
* Default API URL: https://test-api.endorser.ch/api/v2/claim
|
||||
*/
|
||||
|
||||
/// <reference types="node" />
|
||||
// Add at the top of your file to ignore dom types
|
||||
import * as didJwt from 'did-jwt';
|
||||
import { ethers } from 'ethers';
|
||||
import fetch from 'node-fetch';
|
||||
import { program } from 'commander';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
config();
|
||||
|
||||
const admin_keypair = {
|
||||
did: process.env.ADMIN_DID || 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F',
|
||||
privateKey: process.env.ADMIN_PRIVATE_KEY || '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b'
|
||||
};
|
||||
|
||||
// Default values from environment
|
||||
const DEFAULT_ADMIN_DID = admin_keypair.did;
|
||||
const DEFAULT_API_URL = process.env.ENDORSER_API_URL || 'https://test-api.endorser.ch/api/v2/claim';
|
||||
|
||||
/**
|
||||
* Result interface for DID creation process
|
||||
*/
|
||||
interface DIDCreationResult {
|
||||
did: string; // The generated DID
|
||||
privateKey: string; // Private key without 0x prefix
|
||||
publicKey: string; // Public key with 0x prefix
|
||||
isValid: boolean; // Validation status
|
||||
jwt: string; // Signed JWT for registration
|
||||
}
|
||||
|
||||
/**
|
||||
* Result interface for DID registration attempt
|
||||
*/
|
||||
interface RegistrationResult {
|
||||
success: boolean; // Registration success status
|
||||
error?: string; // Optional error message
|
||||
response?: any; // Optional API response data
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and validates a new DID with admin authorization
|
||||
*
|
||||
* Workflow:
|
||||
* 1. Generates new Ethereum keypair using ethers.js
|
||||
* 2. Creates DID from public key in did:ethr format
|
||||
* 3. Constructs registration claim with:
|
||||
* - Admin DID as the agent (authorizer)
|
||||
* - New DID as the participant (being registered)
|
||||
* 4. Signs claim as JWT using admin's private key
|
||||
*
|
||||
* @param adminDid - Administrator DID for authorization
|
||||
* @returns Promise<DIDCreationResult> - Generated DID details and JWT
|
||||
*/
|
||||
async function createAndValidateDID(adminDid: string): Promise<DIDCreationResult> {
|
||||
console.log('Using admin DID:', adminDid);
|
||||
|
||||
// Generate new DID keypair
|
||||
console.log('Generating new keypair...');
|
||||
const wallet = ethers.Wallet.createRandom();
|
||||
const did = `did:ethr:${wallet.address}`;
|
||||
const privateKey = wallet.privateKey.slice(2);
|
||||
const publicKey = wallet.publicKey;
|
||||
|
||||
// Create registration claim with admin as agent
|
||||
const registerClaim = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "RegisterAction",
|
||||
agent: { did: adminDid },
|
||||
participant: { did: did },
|
||||
object: "endorser.ch"
|
||||
};
|
||||
|
||||
const vcPayload = {
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 300,
|
||||
sub: "RegisterAction",
|
||||
vc: {
|
||||
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
||||
type: ["VerifiableCredential"],
|
||||
credentialSubject: registerClaim
|
||||
}
|
||||
};
|
||||
|
||||
console.log('\nGenerated DID Details:');
|
||||
console.log('----------------------');
|
||||
console.log('DID:', did);
|
||||
console.log('Admin DID:', adminDid);
|
||||
console.log('Address:', wallet.address);
|
||||
console.log('Private Key:', wallet.privateKey);
|
||||
console.log('Public Key:', wallet.publicKey);
|
||||
|
||||
console.log('\nDebug Details:');
|
||||
console.log('-------------');
|
||||
console.log('Private Key (hex):', privateKey); // Should be without 0x
|
||||
console.log('Public Key (hex):', publicKey); // Should be with 0x
|
||||
console.log('Header:', {
|
||||
typ: "JWT",
|
||||
alg: "ES256K"
|
||||
});
|
||||
console.log('Payload:', vcPayload);
|
||||
|
||||
// Create and sign JWT with admin's key
|
||||
console.log('\nCreating JWT...');
|
||||
const signer = didJwt.SimpleSigner(admin_keypair.privateKey); // Use admin's private key
|
||||
const jwt = await didJwt.createJWT(vcPayload, {
|
||||
issuer: admin_keypair.did, // Admin DID as issuer
|
||||
signer: signer
|
||||
});
|
||||
|
||||
console.log('\nJWT Parts:');
|
||||
const [header, payload, signature] = jwt.split('.');
|
||||
console.log('Header (base64):', header);
|
||||
console.log('Payload (base64):', payload);
|
||||
console.log('Signature (base64):', signature);
|
||||
|
||||
return { did, privateKey, publicKey, isValid: true, jwt };
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a DID with the endorser.ch API
|
||||
*
|
||||
* Workflow:
|
||||
* 1. Submits JWT to API endpoint
|
||||
* 2. Processes response (success/error)
|
||||
* 3. Handles error cases:
|
||||
* - Network errors
|
||||
* - API errors (400, 401, etc)
|
||||
* - Malformed responses
|
||||
*
|
||||
* @param jwt - Signed JWT for registration
|
||||
* @returns Promise<RegistrationResult> - Registration result with response/error
|
||||
*/
|
||||
async function registerDID(jwt: string): Promise<RegistrationResult> {
|
||||
try {
|
||||
const response = await fetch(DEFAULT_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ jwtEncoded: jwt }),
|
||||
});
|
||||
|
||||
console.log(`\nServer Response Status: ${response.status}`);
|
||||
const responseText = await response.text();
|
||||
console.log(`Server Response Body: ${responseText}`);
|
||||
|
||||
if (response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
response: JSON.parse(responseText)
|
||||
};
|
||||
} else {
|
||||
try {
|
||||
const errorJson = JSON.parse(responseText);
|
||||
const errorMsg = errorJson.error?.message || 'Unknown error';
|
||||
return {
|
||||
success: false,
|
||||
error: `Registration failed (${response.status}): ${errorMsg}`,
|
||||
response: errorJson
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
error: `Registration failed (${response.status}): ${responseText}`,
|
||||
response: responseText
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Request failed: ${e}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Command line handling
|
||||
program
|
||||
.name('did-generator')
|
||||
.description('Generate and register a new DID')
|
||||
.option('-a, --admin-did <did>', 'Admin DID', DEFAULT_ADMIN_DID)
|
||||
.option('--api-url <url>', 'Override API URL', DEFAULT_API_URL)
|
||||
.parse();
|
||||
|
||||
const options = program.opts();
|
||||
const adminDid = options.adminDid; // Use the option instead of args
|
||||
const apiUrl = options.apiUrl;
|
||||
|
||||
console.log('Starting DID Generation...\n');
|
||||
console.log('Using admin DID:', adminDid); // Log the admin DID being used
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const result = await createAndValidateDID(adminDid);
|
||||
console.log('\nSuccessfully generated DID with admin authorization!');
|
||||
console.log('Registration JWT:', result.jwt.substring(0, 50) + '...');
|
||||
|
||||
console.log('\nAttempting registration...');
|
||||
const registrationResult = await registerDID(result.jwt);
|
||||
if (registrationResult.success) {
|
||||
console.log('Registration successful!');
|
||||
console.log('Response:', JSON.stringify(registrationResult.response, null, 2));
|
||||
} else {
|
||||
console.log('Registration failed!');
|
||||
console.log('Error:', registrationResult.error);
|
||||
if (registrationResult.response) {
|
||||
console.log('Full response:', JSON.stringify(registrationResult.response, null, 2));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('\nError:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
@@ -1,284 +0,0 @@
|
||||
#!/bin/bash
|
||||
# DID Visibility Check Script
|
||||
# @author Matthew Raymer
|
||||
#
|
||||
# This script checks visibility permissions for DIDs within the endorser.ch system.
|
||||
# It creates a signed JWT using admin credentials and queries the visibility API.
|
||||
#
|
||||
# Features:
|
||||
# - JWT creation and signing using ES256K-R
|
||||
# - DID visibility checking
|
||||
# - Environment variable support
|
||||
# - Debug logging
|
||||
# - Command line argument parsing
|
||||
#
|
||||
# Usage:
|
||||
# ./dids_seen.sh [-d did_to_check]
|
||||
# DEBUG=1 ./dids_seen.sh # For debug output
|
||||
#
|
||||
# Environment Variables:
|
||||
# ADMIN_DID - Admin DID for authorization
|
||||
# ADMIN_PRIVATE_KEY - Private key for signing
|
||||
# ENDORSER_API_URL - API endpoint (defaults to test)
|
||||
# DEBUG - Enable debug logging when set to 1
|
||||
|
||||
# Enhanced debug logging
|
||||
debug_log() {
|
||||
if [ "${DEBUG:-0}" = "1" ]; then
|
||||
echo "DEBUG: $*" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
# -d: Specific DID to check visibility for
|
||||
CHECK_DID=""
|
||||
while getopts "d:" opt; do
|
||||
case $opt in
|
||||
d) CHECK_DID="$OPTARG" ;;
|
||||
\?) echo "Usage: $0 [-d did_to_check]" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
# Supports:
|
||||
# - ADMIN_DID
|
||||
# - ADMIN_PRIVATE_KEY
|
||||
# - ENDORSER_API_URL
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Default values for required parameters
|
||||
ADMIN_DID=${ADMIN_DID:-"did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"}
|
||||
ADMIN_PRIVATE_KEY=${ADMIN_PRIVATE_KEY:-"2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b"}
|
||||
API_URL=${ENDORSER_API_URL:-"https://test-api.endorser.ch/api/report/whichDidsICanSee"}
|
||||
|
||||
# Create JWT payload with:
|
||||
# - Issuer (iss)
|
||||
# - Subject (sub)
|
||||
# - Issued At (iat)
|
||||
# - Expiration (exp)
|
||||
now=$(date +%s)
|
||||
exp=$((now + 86400)) # 24 hours from now
|
||||
|
||||
payload=$(jq -n \
|
||||
--arg iss "$ADMIN_DID" \
|
||||
--arg sub "$ADMIN_DID" \
|
||||
--arg iat "$now" \
|
||||
--arg exp "$exp" \
|
||||
'{
|
||||
iss: $iss,
|
||||
sub: $sub,
|
||||
iat: ($iat | tonumber),
|
||||
exp: ($exp | tonumber)
|
||||
}')
|
||||
|
||||
# Base64url encode header and payload
|
||||
# Header specifies ES256K-R algorithm for Ethereum compatibility
|
||||
header='{"alg":"ES256K-R","typ":"JWT"}'
|
||||
header_b64=$(echo -n "$header" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
payload_b64=$(echo -n "$payload" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
|
||||
# Create message to sign (header.payload)
|
||||
message="$header_b64.$payload_b64"
|
||||
|
||||
# Add debug points
|
||||
debug_log "Creating JWT with:"
|
||||
debug_log "ADMIN_DID: $ADMIN_DID"
|
||||
debug_log "API_URL: $API_URL"
|
||||
debug_log "Payload: $payload"
|
||||
debug_log "Header base64: $header_b64"
|
||||
debug_log "Payload base64: $payload_b64"
|
||||
debug_log "Message to sign: $message"
|
||||
|
||||
# Create temporary directory for key operations
|
||||
# Uses trap to ensure cleanup on exit
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
# Create private key PEM file
|
||||
# Converts raw private key to PEM format for OpenSSL
|
||||
echo "-----BEGIN EC PRIVATE KEY-----" > "$TMPDIR/private.pem"
|
||||
(
|
||||
echo "302e0201010420" # Private key header
|
||||
echo -n "$ADMIN_PRIVATE_KEY" # Private key bytes
|
||||
echo "a00706052b8104000a" # secp256k1 OID
|
||||
) | xxd -r -p | base64 >> "$TMPDIR/private.pem"
|
||||
echo "-----END EC PRIVATE KEY-----" >> "$TMPDIR/private.pem"
|
||||
|
||||
# Write message to file for signing
|
||||
echo -n "$message" > "$TMPDIR/message.txt"
|
||||
|
||||
# Sign message and get DER format signature
|
||||
openssl dgst -sha256 -sign "$TMPDIR/private.pem" "$TMPDIR/message.txt" > "$TMPDIR/signature.der"
|
||||
|
||||
# Convert DER signature to hex
|
||||
der_sig=$(xxd -p -c 256 "$TMPDIR/signature.der")
|
||||
|
||||
# Parse DER structure
|
||||
# Extracts R and S values from signature
|
||||
if [[ $der_sig =~ ^30([0-9a-f]{2})02([0-9a-f]{2})([0-9a-f]*)02([0-9a-f]{2})([0-9a-f]*)$ ]]; then
|
||||
r_len="${BASH_REMATCH[2]}"
|
||||
r_val="${BASH_REMATCH[3]}"
|
||||
s_len="${BASH_REMATCH[4]}"
|
||||
s_val="${BASH_REMATCH[5]}"
|
||||
|
||||
debug_log "Raw signature values:"
|
||||
debug_log " R (${#r_val} chars): $r_val"
|
||||
debug_log " S (${#s_val} chars): $s_val"
|
||||
|
||||
# Convert lengths to decimal
|
||||
r_len_dec=$((16#$r_len))
|
||||
s_len_dec=$((16#$s_len))
|
||||
|
||||
# Handle R value padding
|
||||
if [ $r_len_dec -gt 32 ]; then
|
||||
r_val=${r_val: -64} # Take last 32 bytes
|
||||
elif [ $r_len_dec -lt 32 ]; then
|
||||
r_val=$(printf "%064s" "$r_val" | tr ' ' '0') # Left pad
|
||||
fi
|
||||
|
||||
# Handle S value padding
|
||||
if [ $s_len_dec -gt 32 ]; then
|
||||
s_val=${s_val: -64} # Take last 32 bytes
|
||||
elif [ $s_len_dec -lt 32 ]; then
|
||||
s_val=$(printf "%064s" "$s_val" | tr ' ' '0') # Left pad
|
||||
fi
|
||||
|
||||
# Ensure both values are exactly 64 characters
|
||||
r_val=$(printf "%064s" "$r_val" | tr ' ' '0')
|
||||
s_val=$(printf "%064s" "$s_val" | tr ' ' '0')
|
||||
|
||||
debug_log "Normalized values:"
|
||||
debug_log " R (${#r_val} chars): $r_val"
|
||||
debug_log " S (${#s_val} chars): $s_val"
|
||||
|
||||
# Create final signature
|
||||
concat_sig="${r_val}${s_val}"
|
||||
|
||||
# Debug the DER parsing
|
||||
debug_log "DER Signature Analysis:"
|
||||
debug_log " Full DER: $der_sig"
|
||||
debug_log " Sequence length: ${BASH_REMATCH[1]}"
|
||||
debug_log " R length: $r_len ($r_len_dec bytes)"
|
||||
debug_log " S length: $s_len ($s_len_dec bytes)"
|
||||
|
||||
# Debug signature components
|
||||
debug_log "Signature components:"
|
||||
debug_log " R value: $r_val (length: ${#r_val})"
|
||||
debug_log " S value: $s_val (length: ${#s_val})"
|
||||
debug_log " Concatenated: $concat_sig (length: ${#concat_sig})"
|
||||
|
||||
# Try both normal and high-S value signatures
|
||||
s_val_alt=""
|
||||
if [ $s_len_dec -gt 32 ]; then
|
||||
# Store alternative S value
|
||||
s_val_alt="$s_val"
|
||||
# Calculate N - s for high-S values
|
||||
n="FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
||||
# Use Python for hex math to avoid bc issues
|
||||
normalized_s=$(python3 -c "
|
||||
n = int('$n', 16)
|
||||
s = int('${s_val}', 16)
|
||||
result = hex(n - s)[2:].zfill(64)
|
||||
print(result.lower())
|
||||
" 2>/dev/null || echo "ERROR")
|
||||
if [ "$normalized_s" = "ERROR" ]; then
|
||||
debug_log " Failed to normalize S value"
|
||||
# Keep original s_val if normalization fails
|
||||
s_val_alt=""
|
||||
else
|
||||
s_val=$(printf "%064s" "$normalized_s" | tr ' ' '0' | tr '[:upper:]' '[:lower:]')
|
||||
debug_log " Normalized S value: $s_val"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Calculate recovery bit (v)
|
||||
# Try each possible recovery value (0-3) until we find one that works
|
||||
for v in {0..3}; do
|
||||
# Try both S values if we have an alternative
|
||||
s_values=("$s_val")
|
||||
if [ -n "$s_val_alt" ]; then
|
||||
s_values+=("$s_val_alt")
|
||||
fi
|
||||
|
||||
for current_s in "${s_values[@]}"; do
|
||||
concat_sig="${r_val}${current_s}"
|
||||
debug_log "Trying with S value: $current_s"
|
||||
|
||||
recovery_sig=$(printf "%s%02x" "$concat_sig" "$v")
|
||||
debug_log "Recovery attempt $v:"
|
||||
debug_log " Concatenated signature: $concat_sig"
|
||||
debug_log " Recovery signature: $recovery_sig"
|
||||
debug_log " Recovery signature length: ${#recovery_sig}"
|
||||
|
||||
# Ensure signature is exactly 65 bytes (130 hex chars)
|
||||
if [ ${#recovery_sig} -ne 130 ]; then
|
||||
debug_log " Invalid signature length: ${#recovery_sig}, expected 130"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Convert hex to binary, then to base64url
|
||||
signature=$(echo -n "$recovery_sig" | xxd -r -p 2>/dev/null | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
debug_log " Base64URL signature: $signature"
|
||||
|
||||
# Create JWT with this signature
|
||||
JWT="$message.$signature"
|
||||
debug_log " Testing JWT: $JWT"
|
||||
|
||||
# Test the JWT against the API
|
||||
response=$(curl -s -X GET "$API_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $JWT")
|
||||
debug_log " API Response: $response"
|
||||
|
||||
# Check if the JWT worked
|
||||
if ! echo "$response" | grep -q "JWT.*failed"; then
|
||||
echo "JWT Token:"
|
||||
echo "$JWT"
|
||||
debug_log "Success with v=$v and S=${current_s:0:8}..."
|
||||
echo
|
||||
echo "Export command:"
|
||||
echo "export TOKEN='$JWT'"
|
||||
echo
|
||||
if [ -n "$CHECK_DID" ]; then
|
||||
# Check if specific DID is in the list
|
||||
if echo "$response" | jq -e --arg did "$CHECK_DID" 'contains([$did])' > /dev/null; then
|
||||
echo "✅ DID $CHECK_DID is in the list"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ DID $CHECK_DID is not in the list"
|
||||
echo "Attempting to add visibility..."
|
||||
|
||||
# Request visibility
|
||||
visibility_response=$(curl -s -X POST \
|
||||
'http://test-api.endorser.ch/api/report/canSeeMe' \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"did\": \"$CHECK_DID\"}")
|
||||
|
||||
if echo "$visibility_response" | grep -q "error"; then
|
||||
echo "❌ Failed to add visibility:"
|
||||
echo "$visibility_response" | jq '.' --indent 2
|
||||
exit 1
|
||||
else
|
||||
echo "✅ Successfully requested visibility. Please try checking again."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Show full list of visible DIDs
|
||||
echo "$response" | jq '.' --indent 2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo "Error: Could not find valid recovery bit"
|
||||
exit 1
|
||||
else
|
||||
echo "Error: Invalid DER signature format"
|
||||
echo "DER: $der_sig"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,175 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Source the environment with DIDs
|
||||
if [ ! -f .generated/test-env.sh ]; then
|
||||
echo "Error: No test environment found. Run run-deeplink-tests.sh first"
|
||||
exit 1
|
||||
fi
|
||||
source .generated/test-env.sh
|
||||
|
||||
# Verify we have the required DIDs
|
||||
if [ -z "$CONTACT1_DID" ] || [ -z "$CONTACT1_KEY" ]; then
|
||||
echo "Error: Contact1 DID info not found in environment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use CONTACT1 as the issuer since we know it's registered
|
||||
REGISTERED_DID="$CONTACT1_DID"
|
||||
REGISTERED_KEY="$CONTACT1_KEY"
|
||||
|
||||
# Default API URL (can be overridden by environment)
|
||||
API_URL=${ENDORSER_API_URL:-"https://test-api.endorser.ch/api/v2/claim"}
|
||||
|
||||
# Function to sign a message using ES256K
|
||||
sign_message() {
|
||||
local message="$1"
|
||||
local private_key="$2"
|
||||
local tmpdir
|
||||
|
||||
# Create temporary directory
|
||||
tmpdir=$(mktemp -d)
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
|
||||
# Debug output
|
||||
echo "Signing message: $message" >&2
|
||||
echo "Using private key: $private_key" >&2
|
||||
|
||||
# Hash the message with SHA-256
|
||||
echo -n "$message" | openssl dgst -sha256 -binary > "$tmpdir/message.hash"
|
||||
|
||||
# Sign the hash directly using the private key
|
||||
# This avoids OpenSSL's PEM format issues with secp256k1
|
||||
python3 -c "
|
||||
import sys
|
||||
from eth_keys import keys
|
||||
import hashlib
|
||||
|
||||
# Read message hash
|
||||
with open('$tmpdir/message.hash', 'rb') as f:
|
||||
message_hash = f.read()
|
||||
|
||||
# Create private key object
|
||||
private_key_bytes = bytes.fromhex('$private_key')
|
||||
private_key = keys.PrivateKey(private_key_bytes)
|
||||
|
||||
# Sign the message hash
|
||||
signature = private_key.sign_msg_hash(message_hash)
|
||||
|
||||
# Output R and S values as hex
|
||||
print(f'{hex(signature.r)[2:]:0>64}')
|
||||
print(f'{hex(signature.s)[2:]:0>64}')
|
||||
" > "$tmpdir/signature.txt"
|
||||
|
||||
# Read R and S values
|
||||
{ read -r r_val; read -r s_val; } < "$tmpdir/signature.txt"
|
||||
|
||||
# Debug R and S values
|
||||
echo "R value: $r_val" >&2
|
||||
echo "S value: $s_val" >&2
|
||||
|
||||
# Concatenate R+S and convert to base64url
|
||||
echo -n "${r_val}${s_val}" | xxd -r -p | base64 -w 0 | tr '/+' '_-' | tr -d '='
|
||||
}
|
||||
|
||||
# Create a test claim payload
|
||||
create_claim_payload() {
|
||||
local issuer_did="$1"
|
||||
|
||||
# Create a test claim matching the app's structure
|
||||
cat << EOF
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TestClaim",
|
||||
"identifier": "test-claim-$(date +%s)",
|
||||
"name": "Test Claim",
|
||||
"description": "Generated test claim",
|
||||
"agent": {
|
||||
"did": "$issuer_did"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Create and sign JWT
|
||||
create_jwt() {
|
||||
local claim="$1"
|
||||
local issuer_did="$2"
|
||||
local private_key="$3"
|
||||
|
||||
# Create header and payload matching the app's structure
|
||||
local header='{"typ":"JWT","alg":"ES256K"}'
|
||||
local payload="{\"iat\":$(date +%s),\"exp\":$(($(date +%s) + 300)),\"iss\":\"$issuer_did\",\"vc\":{\"@context\":[\"https://www.w3.org/2018/credentials/v1\"],\"type\":[\"VerifiableCredential\"],\"credentialSubject\":$claim}}"
|
||||
|
||||
# Base64url encode header and payload
|
||||
local header_b64=$(echo -n "$header" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
local payload_b64=$(echo -n "$payload" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
||||
|
||||
# Create message to sign
|
||||
local message="$header_b64.$payload_b64"
|
||||
|
||||
# Sign message using eth_keys (same as did-jwt library)
|
||||
local signature=$(python3 -c "
|
||||
from eth_keys import keys
|
||||
import hashlib
|
||||
|
||||
# Create private key object
|
||||
private_key_bytes = bytes.fromhex('$private_key')
|
||||
private_key = keys.PrivateKey(private_key_bytes)
|
||||
|
||||
# Hash the message
|
||||
message_hash = hashlib.sha256('$message'.encode()).digest()
|
||||
|
||||
# Sign using ES256K
|
||||
signature = private_key.sign_msg_hash(message_hash)
|
||||
|
||||
# Format as R+S concatenated and base64url encoded
|
||||
import base64
|
||||
signature_bytes = signature.r.to_bytes(32, 'big') + signature.s.to_bytes(32, 'big')
|
||||
print(base64.urlsafe_b64encode(signature_bytes).decode().rstrip('='))
|
||||
")
|
||||
|
||||
# Return complete JWT
|
||||
echo "$message.$signature"
|
||||
}
|
||||
|
||||
# Function to register claim with API
|
||||
register_claim() {
|
||||
local jwt="$1"
|
||||
|
||||
echo "Registering claim with API..."
|
||||
response=$(curl -s -X POST "$API_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"jwtEncoded\":\"$jwt\"}")
|
||||
|
||||
# Check response
|
||||
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
echo "Registration successful!"
|
||||
echo "Response:"
|
||||
echo "$response" | jq '.'
|
||||
return 0
|
||||
else
|
||||
echo "Registration failed!"
|
||||
echo "Error: $(echo "$response" | jq -r '.error.message // empty')"
|
||||
echo "Full response:"
|
||||
echo "$response" | jq '.' || echo "$response"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
echo "Generating test claim..."
|
||||
|
||||
# Create claim payload
|
||||
claim=$(create_claim_payload "$REGISTERED_DID")
|
||||
echo "Claim payload:"
|
||||
echo "$claim" | jq '.'
|
||||
|
||||
# Create and sign JWT
|
||||
echo "Generating signed JWT..."
|
||||
jwt=$(create_jwt "$claim" "$REGISTERED_DID" "$REGISTERED_KEY")
|
||||
|
||||
# Output just the JWT for piping
|
||||
echo "$jwt"
|
||||
|
||||
# Register the claim
|
||||
register_claim "$jwt"
|
||||
@@ -1 +0,0 @@
|
||||
usage gas they pyramid walnut mammal absorb major crystal nurse element congress assist panic bomb entire area slogan film educate decrease buddy describe finish
|
||||
@@ -14,7 +14,7 @@ YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Parse command line arguments
|
||||
TIMEOUT=5
|
||||
TIMEOUT=10
|
||||
TEST_MODE="execute" # Default to execute mode
|
||||
|
||||
while getopts "t:p" opt; do
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Helper script for secp256k1 signing using pure shell commands
|
||||
|
||||
PRIVATE_KEY_FILE="$1"
|
||||
MESSAGE_HASH_FILE="$2"
|
||||
|
||||
# Load private key and message hash
|
||||
PRIVATE_KEY=$(cat "$PRIVATE_KEY_FILE" | xxd -p -c 64)
|
||||
MESSAGE_HASH=$(cat "$MESSAGE_HASH_FILE" | xxd -p -c 32)
|
||||
|
||||
# Use secp256k1 library through Python (as a last resort)
|
||||
python3 -c "
|
||||
from coincurve import PrivateKey
|
||||
private_key = PrivateKey(bytes.fromhex('$PRIVATE_KEY'))
|
||||
signature = private_key.sign(bytes.fromhex('$MESSAGE_HASH'), hasher=None)
|
||||
print(signature.hex())
|
||||
" | xxd -r -p | base64 -w 0 | tr '/+' '_-' | tr -d '='
|
||||
@@ -1,130 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Verify required environment variables are set
|
||||
required_vars=(
|
||||
"CONTACT1_DID"
|
||||
"CONTACT1_KEY"
|
||||
"CONTACT2_DID"
|
||||
"CONTACT2_KEY"
|
||||
"ISSUER_DID"
|
||||
"ISSUER_KEY"
|
||||
"CONTACTS_JSON"
|
||||
)
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
if [ -z "${!var}" ]; then
|
||||
echo "Error: $var is not set"
|
||||
echo "Please run: source .generated/test-env.sh"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Color definitions
|
||||
BLUE='\033[0;34m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
PURPLE='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Parse command line arguments
|
||||
TIMEOUT=5
|
||||
ALL_TESTS=false
|
||||
TEST_MODE=${TEST_MODE:-execute} # Default to execute mode
|
||||
|
||||
while getopts "t:a" opt; do
|
||||
case $opt in
|
||||
t) TIMEOUT=$OPTARG ;;
|
||||
a) ALL_TESTS=true ;;
|
||||
\?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check for adb and connected devices
|
||||
if [ "$TEST_MODE" = "execute" ]; then
|
||||
if ! command -v adb >/dev/null 2>&1; then
|
||||
echo "Warning: adb not found, switching to print mode"
|
||||
TEST_MODE=print
|
||||
elif [ -z "$(adb devices | grep -v List | grep device)" ]; then
|
||||
echo "Warning: no devices/emulators found, switching to print mode"
|
||||
TEST_MODE=print
|
||||
fi
|
||||
fi
|
||||
|
||||
# Function to encode URL parameters
|
||||
urlencode() {
|
||||
local string="${1}"
|
||||
local strlen=${#string}
|
||||
local encoded=""
|
||||
local pos c o
|
||||
|
||||
for (( pos=0 ; pos<strlen ; pos++ )); do
|
||||
c=${string:$pos:1}
|
||||
case "$c" in
|
||||
[-_.~a-zA-Z0-9] ) o="${c}" ;;
|
||||
* ) printf -v o '%%%02x' "'$c"
|
||||
esac
|
||||
encoded+="${o}"
|
||||
done
|
||||
echo "${encoded}"
|
||||
}
|
||||
|
||||
# Function to print section header
|
||||
print_header() {
|
||||
echo -e "\n${BOLD}${1}${NC}"
|
||||
echo -e "${BOLD}$(printf '=%.0s' {1..50})${NC}\n"
|
||||
}
|
||||
|
||||
# Function to handle deep links
|
||||
handle_deeplink() {
|
||||
local url="$1"
|
||||
local color="$2"
|
||||
local encoded_url=$(urlencode "$url")
|
||||
|
||||
if [ "$TEST_MODE" = "print" ]; then
|
||||
echo -e "${color}Deep Link URL:${NC}"
|
||||
echo -e "${color}$url${NC}"
|
||||
echo -e "${color}Encoded URL:${NC}"
|
||||
echo -e "${color}$encoded_url${NC}"
|
||||
echo "---"
|
||||
else
|
||||
echo -e "${color}Opening: $url${NC}"
|
||||
adb shell am start -a android.intent.action.VIEW -d "$encoded_url"
|
||||
sleep "$TIMEOUT"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate a test JWT for claims
|
||||
TEST_JWT=$(./test-scripts/did_generator.sh | grep "Created JWT:" | cut -d' ' -f3)
|
||||
|
||||
echo -e "${BOLD}Running deep link tests (mode: $TEST_MODE)...${NC}"
|
||||
|
||||
# 1. Basic routes
|
||||
print_header "Basic Routes"
|
||||
handle_deeplink "timesafari://claim-cert/$TEST_JWT" $BLUE
|
||||
handle_deeplink "timesafari://claim-add-raw/$TEST_JWT?claim=$(urlencode '{"type":"test"}')&claimJwtId=$TEST_JWT" $BLUE
|
||||
|
||||
# 2. Contact import routes
|
||||
print_header "Contact Import Routes"
|
||||
handle_deeplink "timesafari://contacts/import?contacts=$(urlencode "$CONTACTS_JSON")" $GREEN
|
||||
handle_deeplink "timesafari://contacts" $GREEN
|
||||
|
||||
# 3. Contact management routes
|
||||
print_header "Contact Management Routes"
|
||||
handle_deeplink "timesafari://contact-edit?did=$CONTACT1_DID" $YELLOW
|
||||
handle_deeplink "timesafari://contact-edit?did=$CONTACT2_DID" $YELLOW
|
||||
|
||||
if [ "$ALL_TESTS" = true ]; then
|
||||
# 4. Claims and verification routes
|
||||
print_header "Claims and Verification Routes"
|
||||
handle_deeplink "timesafari://verify?issuer=$ISSUER_DID" $PURPLE
|
||||
handle_deeplink "timesafari://claims?contact=$CONTACT1_DID" $PURPLE
|
||||
|
||||
# 5. Project routes
|
||||
print_header "Project Routes"
|
||||
handle_deeplink "timesafari://projects" $CYAN
|
||||
handle_deeplink "timesafari://project-edit?id=test-project" $CYAN
|
||||
fi
|
||||
|
||||
echo -e "\n${BOLD}Deep link tests completed${NC}"
|
||||
Reference in New Issue
Block a user