> For the complete documentation index, see [llms.txt](https://constellation-main.gitbook.io/digital-evidence/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://constellation-main.gitbook.io/digital-evidence/sign-and-submit-data.md).

# Sign and Submit Data

## Sign and submit your data

This guide walks you through integrating the Digital Evidence platform into your application. You'll learn how to hash your data, construct fingerprints, sign payloads, and submit them to the service.

### Prerequisites

* Your organization and tenant IDs (provided by Constellation)
* An API key (provided by Constellation)

### Install the SDK

{% tabs %}
{% tab title="TypeScript" %}

```typescript
npm install @constellation-network/digital-evidence-sdk
```

{% endtab %}

{% tab title="Python" %}

```python
pip install constellation-digital-evidence-sdk
# For network/submission features:
pip install constellation-digital-evidence-sdk[network]
```

{% endtab %}
{% endtabs %}

The SDK handles all cryptographic operations internally — key generation, RFC 8785 JSON canonicalization, and the `SECP256K1_RFC8785_V1` signature protocol — so you don't need to implement the signing steps yourself.

***

## Quick Start <a href="#quick-start" id="quick-start"></a>

The fastest way to submit a fingerprint end-to-end:

{% tabs %}
{% tab title="TypeScript" %}

```typescript
import {
  generateFingerprint,
  generateKeyPair,
} from '@constellation-network/digital-evidence-sdk';
import { DedClient } from '@constellation-network/digital-evidence-sdk/network';

// 1. Generate a keypair (or use an existing private key)
const keyPair = generateKeyPair();

// 2. Create and sign a fingerprint in one call
const submission = await generateFingerprint({
  orgId: '<ORG_ID>',
  tenantId: '<TENANT_ID>',
  eventId: crypto.randomUUID(),
  documentId: 'contract-2024-001',
  documentContent: 'This is my document content',
  includeMetadata: true,
  tags: { department: 'legal', priority: 'high' },
}, keyPair.privateKey);

// 3. Submit via the SDK client
const client = new DedClient({
  baseUrl: 'https://de-api.constellationnetwork.io',
  apiKey: '<API_KEY>',
});

const results = await client.fingerprints.submit([submission]);
console.log(results);
// [{ event_id: "...", hash: "abc123...", accepted: true, errors: [] }]
```

{% endtab %}

{% tab title="Python" %}

```python
from constellation_digital_evidence_sdk import generate_fingerprint, GenerateOptions
from constellation_digital_evidence_sdk.network import DedClient, DedClientConfig
from constellation_sdk import generate_key_pair
import uuid

# 1. Generate a keypair (or use an existing private key)
key_pair = generate_key_pair()

# 2. Create and sign a fingerprint in one call
submission = generate_fingerprint(
    GenerateOptions(
        org_id="550e8400-e29b-41d4-a716-446655440000",
        tenant_id="123e4567-e89b-12d3-a456-426614174000",
        event_id=str(uuid.uuid4()),
        document_id="contract-2024-001",
        document_content="This is my document content",
        include_metadata=True,
        tags={"department": "legal", "priority": "high"},
    ),
    key_pair.private_key,
)

# 3. Submit via the SDK client
async with DedClient(DedClientConfig(
    base_url="https://de-api.constellationnetwork.io",
    api_key="your-api-key",
)) as client:
    results = await client.fingerprints.submit([submission])
    print(results)

```

{% endtab %}
{% endtabs %}

That's it. The SDK handles hashing the document content, constructing the fingerprint, canonicalizing the JSON, and signing with the correct protocol.

***

## Step-by-Step Guide <a href="#step-by-step-guide" id="step-by-step-guide"></a>

### Step 1: Generate or Load a Keypair <a href="#step-1-generate-or-load-a-keypair" id="step-1-generate-or-load-a-keypair"></a>

Each submitter needs a SECP256K1 keypair. The SDK can generate one, or you can use an existing private key.

{% tabs %}
{% tab title="TypeScript" %}

```typescript
import { generateKeyPair } from '@constellation-network/digital-evidence-sdk';

// Generate a new keypair
const keyPair = generateKeyPair();
console.log('Private key:', keyPair.privateKey);
console.log('Public key:', keyPair.publicKey);

// Or use an existing private key (64-char hex string)
const existingPrivateKey = 'your-64-char-hex-private-key';
```

{% endtab %}

{% tab title="Python" %}

```python
from constellation_sdk import generate_key_pair

key_pair = generate_key_pair()
print('Private key:', key_pair.private_key)
print('Public key:', key_pair.public_key)
```

{% endtab %}
{% endtabs %}

{% hint style="info" icon="message-exclamation" %}
\
Store your private key securely. Anyone with the key can sign fingerprints on your behalf. Never commit private keys to source control.
{% endhint %}

### Step 2: Hash Your Raw Data <a href="#step-2-hash-your-raw-data" id="step-2-hash-your-raw-data"></a>

Compute a cryptographic hash of your original document or data payload. This hash becomes the `documentRef` field in your fingerprint. Any hash algorithm that produces a 64-byte or smaller hex string is acceptable (SHA-256, SHA3-256, BLAKE3, etc.).

{% tabs %}
{% tab title="TypeScript" %}

```typescript
import { hashDocument } from '@constellation-network/digital-evidence-sdk';

// SDK provides a built-in SHA-256 hasher
const documentRef = hashDocument('Your document content here');
// "a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789a"
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib

document_content = b"Your document content here"
document_ref = hashlib.sha256(document_content).hexdigest()
```

{% endtab %}
{% endtabs %}

### Step 3: Create and Sign the Fingerprint

{% tabs %}
{% tab title="TypeScript" %}

```typescript
import {
  createFingerprintValue,
  signFingerprint,
  hashDocument,
  getPublicKeyId,
} from '@constellation-network/digital-evidence-sdk';

// 1. Hash the document
const documentRef = hashDocument('Your document content here');

// 2. Create the fingerprint value
const value = createFingerprintValue({
  orgId: '<ORG_ID>',
  tenantId: '<TENANT_ID>',
  eventId: crypto.randomUUID(),
  documentId: 'contract-2024-001',
  documentRef,
}, getPublicKeyId(privateKey));

// 3. Sign it
const signedFingerprint = await signFingerprint(value, privateKey);

// 4. Package as a submission
const submission = { attestation: signedFingerprint };
```

{% endtab %}

{% tab title="Python" %}

```python
from constellation_digital_evidence_sdk import generate_fingerprint, GenerateOptions
import uuid

submission = generate_fingerprint(
    GenerateOptions(
        org_id="<ORG_ID>",
        tenant_id="<TENANT_ID>",
        event_id=str(uuid.uuid4()),
        document_id="contract-2024-001",
        document_content="Your document content here",
        include_metadata=True,
        tags={"department": "legal"},
    ),
    private_key,
)
```

{% endtab %}
{% endtabs %}

### Step 4: Submit to the API <a href="#step-4-submit-to-the-api" id="step-4-submit-to-the-api"></a>

{% tabs %}
{% tab title="TypeScript" %}

```typescript
import { DedClient } from '@constellation-network/digital-evidence-sdk/network';

const client = new DedClient({
  baseUrl: 'https://de-api.constellationnetwork.io',
  apiKey: '<API_KEY>',
});

// Submit one or more fingerprints (up to 100 per request)
const results = await client.fingerprints.submit([submission]);

for (const result of results) {
  if (result.accepted) {
    console.log(`Accepted: hash=${result.hash}`);
  } else {
    console.error(`Rejected: ${result.errors.join(', ')}`);
  }
}
```

{% endtab %}

{% tab title="Python" %}

```python
from constellation_digital_evidence_sdk.network import DedClient, DedClientConfig

async with DedClient(DedClientConfig(
    base_url="https://de-api.constellationnetwork.io",
    api_key="<API_KEY>",
)) as client:
    results = await client.fingerprints.submit([submission])

    for result in results:
        if result.accepted:
            print(f"Accepted: hash={result.hash}")
        else:
            print(f"Rejected: {result.errors}")
```

{% endtab %}
{% endtabs %}

**Submission Response**

Each fingerprint in the request gets a result:

```json
[
  {
    "event_id": "7ca8c920-0ead-22e2-91c5-11d05fe540d9",
    "hash": "5c5356a4229fc793fd6c342976e70286c99d1c6d920ab935cadfea512210847c",
    "accepted": true,
    "errors": []
  }
]
```

If a fingerprint is rejected, `accepted` will be `false` and `errors` will list the reasons. See the [FAQ & Troubleshooting](/digital-evidence/faq-and-troubleshooting.md) for common error messages and fixes.

***

## Document Storage <a href="#document-storage" id="document-storage"></a>

In addition to fingerprinting, you can attach original documents alongside your submissions. Documents are stored securely in S3 and can be retrieved later via presigned download URLs.

### Upload a Document <a href="#upload-a-document" id="upload-a-document"></a>

Use multipart form upload to submit fingerprints with their source documents:

`POST /v1/fingerprints/upload`

{% tabs %}
{% tab title="TypeScript" %}

```typescript
import { readFileSync } from 'fs';
import {
  generateFingerprint,
  generateKeyPair,
  hashDocument,
} from '@constellation-network/digital-evidence-sdk';
import { DedClient } from '@constellation-network/digital-evidence-sdk/network';

// 1. Read the document and compute its SHA-256 hash
const documentBytes = readFileSync('contract.pdf');
const documentHash = hashDocument(documentBytes);

// 2. Generate a signed fingerprint with the document hash as documentRef
const { privateKey } = generateKeyPair();
const submission = await generateFingerprint({
  orgId: '<ORG_ID>',
  tenantId: '<TENANT_ID>',
  eventId: crypto.randomUUID(),
  documentId: 'contract-2024-001',
  documentRef: documentHash,
  includeMetadata: true,
}, privateKey);

// 3. Upload via the SDK client
const client = new DedClient({
  baseUrl: 'https://de-api.constellationnetwork.io',
  apiKey: '<API_KEY>',
});

const documents = new Map([
  [documentHash, {
    blob: new Blob([documentBytes]),
    mimeType: 'application/pdf',
  }],
]);

const { data: results } = await client.fingerprints.upload(
  [submission],
  documents,
);

console.log(results);
// [{
//   eventId: "...",
//   hash: "abc123...",
//   accepted: true,
//   document: {
//     s3Key: "orgId/tenantId/2024/06/eventId",
//     contentType: "application/pdf",
//     fileSize: 102400,
//     uploadedAt: "2024-06-15T12:34:56Z"
//   },
//   errors: []
// }]
```

{% endtab %}

{% tab title="Python" %}

```python
import asyncio
import hashlib
import json
from uuid import uuid4
from datetime import datetime, timezone

import aiohttp
import rfc8785
from ecdsa import SigningKey, SECP256k1
from ecdsa.util import sigencode_der


def sign_fingerprint_value(fingerprint_value: dict, private_key: SigningKey) -> dict:
    """Sign a fingerprint value using SECP256K1_RFC8785_V1 protocol."""
    canonical_bytes = rfc8785.dumps(fingerprint_value)
    hash_bytes = hashlib.sha256(canonical_bytes).digest()
    hash_hex_bytes = hash_bytes.hex().encode("utf-8")
    truncated = hashlib.sha512(hash_hex_bytes).digest()[:32]

    signature = private_key.sign_digest(truncated, sigencode=sigencode_der)

    vk = private_key.verifying_key
    x, y = vk.pubkey.point.x(), vk.pubkey.point.y()
    public_key_hex = f"04{x:064x}{y:064x}"

    return {
        "attestation": {
            "content": fingerprint_value,
            "proofs": [{
                "id": public_key_hex,
                "signature": signature.hex(),
                "algorithm": "SECP256K1_RFC8785_V1",
            }],
        }
    }


def build_multipart_body(fingerprints_json: bytes, doc_hash: str,
                         document_bytes: bytes, filename: str,
                         mime_type: str) -> tuple[bytes, str]:
    """Build raw multipart body with per-part Content-Length headers."""
    boundary = f"----PythonDed{uuid4().hex}"
    parts = []

    # Fingerprints JSON part
    parts.append(
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="fingerprints"\r\n'
        f"Content-Type: application/json\r\n"
        f"Content-Length: {len(fingerprints_json)}\r\n"
        f"\r\n"
    )
    parts.append(fingerprints_json)
    parts.append(b"\r\n")

    # Document part (name = SHA-256 hash of document)
    parts.append(
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="{doc_hash}"; filename="{filename}"\r\n'
        f"Content-Type: {mime_type}\r\n"
        f"Content-Length: {len(document_bytes)}\r\n"
        f"\r\n"
    )
    parts.append(document_bytes)
    parts.append(f"\r\n--{boundary}--\r\n")

    body = b"".join(p.encode("utf-8") if isinstance(p, str) else p for p in parts)
    return body, f"multipart/form-data; boundary={boundary}"


async def upload_document():
    private_key = SigningKey.generate(curve=SECP256k1)

    # 1. Read the document and compute its SHA-256 hash
    with open("contract.pdf", "rb") as f:
        document_bytes = f.read()
    doc_hash = hashlib.sha256(document_bytes).hexdigest()

    # 2. Build and sign the fingerprint
    fingerprint_value = {
        "orgId": "<ORG_ID>",
        "tenantId": "<TENANT_ID>",
        "eventId": str(uuid4()),
        "documentId": "contract-2024-001",
        "documentRef": doc_hash,
        "timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
        "version": 1,
    }
    submission = sign_fingerprint_value(fingerprint_value, private_key)

    # 3. Build multipart body with per-part Content-Length headers
    fingerprints_json = json.dumps([submission]).encode("utf-8")
    body, content_type = build_multipart_body(
        fingerprints_json, doc_hash, document_bytes, "contract.pdf", "application/pdf"
    )

    # 4. Upload
    async with aiohttp.ClientSession() as session:
        async with session.post(
            "https://de-api.constellationnetwork.io/v1/fingerprints/upload",
            data=body,
            headers={
                "X-Api-Key": "<API_KEY>",
                "Content-Type": content_type,
            },
        ) as resp:
            result = await resp.json()
            print(json.dumps(result, indent=2))

asyncio.run(upload_document())
```

{% endtab %}
{% endtabs %}

**Key rules:**

* The document part name **must** be the SHA-256 hash of the document content (matching `documentRef` in the fingerprint)
* Each document part must include a `Content-Type` header

**Supported file types:**

| Category  | Types                      |
| --------- | -------------------------- |
| Documents | PDF, DOCX, plain text, CSV |
| Images    | JPEG, PNG, GIF, WebP       |
| Data      | JSON, XML                  |

**Limits:**

* Maximum file size: 1 GB per document
* Credit cost: 1 credit per 100 KB (minimum 1 credit per document)

### Download a Document <a href="#download-a-document" id="download-a-document"></a>

Retrieve the original document for any fingerprint that included a document upload:

`GET /v1/fingerprints/{event_id}/document`

{% tabs %}
{% tab title="TypeScript" %}

```typescript
// Public endpoint - no auth needed
const response = await fetch(
  `https://de-api.constellationnetwork.io/v1/fingerprints/${eventId}/document`,
  { redirect: 'follow' }
);
const documentBlob = await response.blob();
```

{% endtab %}

{% tab title="Python" %}

```python
import aiohttp
import asyncio

async def download_document(event_id: str, output_path: str):
    url = f"https://de-api.constellationnetwork.io/v1/fingerprints/{event_id}/document"

    async with aiohttp.ClientSession() as session:
        async with session.get(url, allow_redirects=True) as resp:
            with open(output_path, "wb") as f:
                f.write(await resp.read())

asyncio.run(download_document("4ca56b60-f01c-4092-a4be-ce70ff7ec92b", "downloaded-document.pdf"))
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -L "https://de-api.constellationnetwork.io/v1/fingerprints/${EVENT_ID}/document" \
  -o downloaded-document.pdf
```

{% endtab %}
{% endtabs %}

***

#### FingerprintValue Fields

| Field         | Type     | Description                      | Required |
| ------------- | -------- | -------------------------------- | -------- |
| `orgId`       | UUID     | Your organization ID             | Yes      |
| `tenantId`    | UUID     | Your tenant ID                   | Yes      |
| `eventId`     | UUID     | Unique event identifier          | Yes      |
| `documentId`  | String   | Your document identifier         | Yes      |
| `documentRef` | String   | SHA-256 hash of document content | Yes      |
| `timestamp`   | ISO 8601 | Creation timestamp (UTC)         | Yes      |
| `version`     | Integer  | Schema version (≥ 1)             | Yes      |
| `signer_id`   | String   | Public key (hex)                 | No       |

#### SignatureProof Fields

| Field       | Type   | Description              | Required |
| ----------- | ------ | ------------------------ | -------- |
| `id`        | String | Public key identifier    | Yes      |
| `signature` | String | ECDSA signature (hex)    | Yes      |
| `algorithm` | String | "SECP256K1\_RFC8785\_V1" | Yes      |

### Key Points

1. **Signature Algorithm**: Must use `SECP256K1_RFC8785_V1`
2. **Canonicalization**: JSON must be canonicalized following [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) exactly for deterministic serialization
3. **Timestamps**: Use ISO 8601 format with UTC timezone
4. **UUIDs**: All IDs should be valid UUIDv4 format

***

## Additional Resources

* **API Specification:** [API Specification](/digital-evidence/api-specification.md)
* **Signature Protocol Specification:** [Signature Protocol Guide](https://github.com/Constellation-Labs/metakit/blob/dev/docs/sig-spec/signature-protocol-developer-guide.md)
* **JSON Canonicalization:** [RFC 8785 Canonicalization Scheme](https://datatracker.ietf.org/doc/html/rfc8785)
* **Data Schema via** Protocol Buffer definitions

{% file src="/files/LgWwbM402hncskJy4gwZ" %}
