Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Lockb0x.Anchor.Stellar/StellarAnchorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ public async Task<bool> VerifyAnchorAsync(
return false;
}

if (!string.Equals(anchor.HashAlgorithm, "sha-256", StringComparison.OrdinalIgnoreCase))
if (!string.Equals(anchor.HashAlgorithm, "SHA256", StringComparison.OrdinalIgnoreCase))
{
return false;
}

var account = ResolvePublicKey(stellarPublicKey, networkOptions);
var memo = _memoStrategy.CreateMemo(entry, account, _canonicalizer);

var record = await _horizonClient.GetTransactionAsync(anchor.TransactionHash, networkOptions, cancellationToken).ConfigureAwait(false);
var record = await _horizonClient.GetTransactionAsync(anchor.Reference, networkOptions, cancellationToken).ConfigureAwait(false);
if (record is null)
{
return false;
Expand Down Expand Up @@ -148,7 +148,7 @@ public Task<string> GetTransactionUrlAsync(
}

var networkOptions = ResolveNetwork(network);
var url = networkOptions.GetExplorerUrl(anchor.TransactionHash);
var url = networkOptions.GetExplorerUrl(anchor.Reference);
return Task.FromResult(url);
}

Expand Down Expand Up @@ -210,8 +210,8 @@ private AnchorProof BuildAnchorProof(StellarTransactionRecord record, StellarNet
return new AnchorProof
{
Chain = network.ChainId,
TransactionHash = record.Hash,
HashAlgorithm = "sha-256",
Reference = record.Hash,
HashAlgorithm = "SHA256",
AnchoredAt = record.LedgerCloseTime ?? _clock.UtcNow
};
}
Expand Down
129 changes: 118 additions & 11 deletions Lockb0x.Api/Controllers/CodexController.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,131 @@
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using Lockb0x.Core.Canonicalization;
using Lockb0x.Core.Models;
using Lockb0x.Core.Validation;
using Microsoft.AspNetCore.Mvc;
using Lockb0x.Core;

namespace Lockb0x.Api.Controllers;

[ApiController]
[Route("api/[controller]")]
public class CodexController : ControllerBase
public sealed class CodexController : ControllerBase
{
private readonly ICodexEntryValidator _validator;
private readonly IJsonCanonicalizer _canonicalizer;

public CodexController(ICodexEntryValidator validator, IJsonCanonicalizer canonicalizer)
{
_validator = validator;
_canonicalizer = canonicalizer;
}

/// <summary>
/// Validate a Codex Entry document and return canonical representation details.
/// </summary>
[HttpPost("create")]
public IActionResult Create([FromBody] object file) => StatusCode(501);
[ProducesResponseType(typeof(CodexEntryEnvelope), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public ActionResult<CodexEntryEnvelope> Create([FromBody] CodexEntry entry)
{
var validation = _validator.Validate(entry);
if (!validation.Success)
{
return BadRequest(ValidationSummary.From(validation));
}

[HttpPost("sign")]
public IActionResult Sign([FromBody] CodexEntry entry) => StatusCode(501);
var canonicalJson = _canonicalizer.Canonicalize(entry);
var hash = _canonicalizer.Hash(entry, HashAlgorithmName.SHA256);
var envelope = new CodexEntryEnvelope(entry, canonicalJson, ToBase64Url(hash));
return Ok(envelope);
}

[HttpPost("anchor")]
public IActionResult Anchor([FromBody] CodexEntry entry) => StatusCode(501);
/// <summary>
/// Validate a Codex Entry document and return validation diagnostics.
/// </summary>
[HttpPost("validate")]
[ProducesResponseType(typeof(ValidationSummary), StatusCodes.Status200OK)]
public ActionResult<ValidationSummary> Validate([FromBody] CodexEntry entry, [FromQuery(Name = "anchor_network")] string? anchorNetwork)
{
var context = string.IsNullOrWhiteSpace(anchorNetwork) ? null : new CodexEntryValidationContext(anchorNetwork);
var result = _validator.Validate(entry, context);
return Ok(ValidationSummary.From(result));
}

[HttpPost("certify")]
public IActionResult Certify([FromBody] CodexEntry entry) => StatusCode(501);
/// <summary>
/// Provide a schema-aligned template illustrating the latest fields (anchor_ref, protected headers, ni-URI storage descriptors).
/// </summary>
[HttpGet("template")]
[ProducesResponseType(typeof(CodexEntryEnvelope), StatusCodes.Status200OK)]
public ActionResult<CodexEntryEnvelope> Template()
{
var now = DateTimeOffset.UtcNow;
var entry = new CodexEntryBuilder()
.WithId(Guid.NewGuid())
.WithVersion("1.0")
.WithStorage(new StorageDescriptor
{
Protocol = "ipfs",
IntegrityProof = "ni:///sha-256;example",
MediaType = "application/json",
SizeBytes = 1024,
Location = new StorageLocation
{
Region = "us-east-1",
Jurisdiction = "US",
Provider = "ipfs"
}
})
.WithIdentity(new IdentityDescriptor
{
Org = "did:example:issuer",
Artifact = "example-artifact"
})
.WithTimestamp(now)
.WithAnchor(new AnchorProof
{
Chain = "stellar:testnet",
Reference = "0000000000000000000000000000000000000000000000000000000000000000",
HashAlgorithm = "SHA256"
})
.WithSignatures(new[]
{
new SignatureProof
{
Protected = new SignatureProtectedHeader
{
Algorithm = "EdDSA",
KeyId = "did:example:issuer#ed25519"
},
Signature = "base64url-signature"
}
})
.Build();

[HttpPost("verify")]
public IActionResult Verify([FromBody] CodexEntry entry) => StatusCode(501);
var canonicalJson = _canonicalizer.Canonicalize(entry);
var hash = _canonicalizer.Hash(entry, HashAlgorithmName.SHA256);
return Ok(new CodexEntryEnvelope(entry, canonicalJson, ToBase64Url(hash)));
}

private static string ToBase64Url(ReadOnlySpan<byte> value)
{
return Convert.ToBase64String(value)
.TrimEnd('=')
.Replace('+', '-', StringComparison.Ordinal)
.Replace('/', '_', StringComparison.Ordinal);
}
}

public sealed record CodexEntryEnvelope(
[property: JsonPropertyName("entry")] CodexEntry Entry,
[property: JsonPropertyName("canonical_json")] string CanonicalJson,
[property: JsonPropertyName("canonical_hash")] string CanonicalHashBase64Url);

public sealed record ValidationSummary(
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("errors")] IReadOnlyList<CodexEntryValidationError> Errors,
[property: JsonPropertyName("warnings")] IReadOnlyList<CodexEntryValidationWarning> Warnings)
{
public static ValidationSummary From(ValidationResult result)
=> new(result.Success, result.Errors, result.Warnings);
}
81 changes: 77 additions & 4 deletions Lockb0x.Api/Lockb0x.Api.http
Original file line number Diff line number Diff line change
@@ -1,6 +1,79 @@
@Lockb0x.Api_HostAddress = http://localhost:5004

GET {{Lockb0x.Api_HostAddress}}/weatherforecast/
Accept: application/json

###
### Create (validate + canonicalize) a Codex Entry
POST {{Lockb0x.Api_HostAddress}}/api/codex/create
Content-Type: application/json

{
"id": "2d5f4b5e-1bfa-43ea-9d0a-6d6b79b3a9b2",
"version": "1.0",
"storage": {
"protocol": "ipfs",
"integrity_proof": "ni:///sha-256;abcdef",
"media_type": "application/json",
"size_bytes": 1024,
"location": {
"region": "us-east-1",
"jurisdiction": "US",
"provider": "ipfs"
}
},
"identity": {
"org": "did:example:issuer",
"artifact": "example-artifact"
},
"timestamp": "2025-10-01T00:00:00Z",
"anchor": {
"chain": "stellar:testnet",
"anchor_ref": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"hash_alg": "SHA256"
},
"signatures": [
{
"protected": {
"alg": "EdDSA",
"kid": "did:example:issuer#ed25519"
},
"signature": "base64urlsignature"
}
]
}

### Validate only
POST {{Lockb0x.Api_HostAddress}}/api/codex/validate?anchor_network=stellar:testnet
Content-Type: application/json

{
"id": "2d5f4b5e-1bfa-43ea-9d0a-6d6b79b3a9b2",
"version": "1.0",
"storage": {
"protocol": "ipfs",
"integrity_proof": "ni:///sha-256;abcdef",
"media_type": "application/json",
"size_bytes": 1024,
"location": {
"region": "us-east-1",
"jurisdiction": "US",
"provider": "ipfs"
}
},
"identity": {
"org": "did:example:issuer",
"artifact": "example-artifact"
},
"timestamp": "2025-10-01T00:00:00Z",
"anchor": {
"chain": "stellar:testnet",
"anchor_ref": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"hash_alg": "SHA256"
},
"signatures": [
{
"protected": {
"alg": "EdDSA",
"kid": "did:example:issuer#ed25519"
},
"signature": "base64urlsignature"
}
]
}
65 changes: 24 additions & 41 deletions Lockb0x.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}

app.UseHttpsRedirection();

var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
using Lockb0x.Core.Canonicalization;
using Lockb0x.Core.Validation;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddSingleton<IJsonCanonicalizer, JcsCanonicalizer>();
builder.Services.AddSingleton<ICodexEntryValidator, CodexEntryValidator>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.MapControllers();

app.Run();
30 changes: 21 additions & 9 deletions Lockb0x.Api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,33 @@ Lockb0x.Api exposes the Lockb0x protocol as a web API for agentic, automated, an

## Key Features

- RESTful endpoints for protocol operations
- Integration with all core modules (Core, Signing, Storage, Anchor, Certificates, Verifier)
- Configurable for development and production
- Extensible controller and middleware architecture
- `POST /api/codex/create` validates a Codex Entry payload (with `anchor_ref`, protected signature headers, ni-URI storage descriptors) and returns canonical JSON plus a base64url SHA-256 hash.
- `POST /api/codex/validate` returns structured validation results (errors + warnings) with optional anchor network hints.
- `GET /api/codex/template` supplies a schema-aligned template entry for agent onboarding.
- Swagger/OpenAPI metadata enabled in development for quick contract discovery.

## Usage

- Use API endpoints to automate protocol flows
- Integrate with agentic clients, CLI, and external systems
- See OpenAPI documentation for endpoint details
### Validate and Canonicalize an Entry

```bash
curl -X POST http://localhost:5000/api/codex/create \
-H "Content-Type: application/json" \
-d @entry.json
```

### Validation Only

```bash
curl -X POST "http://localhost:5000/api/codex/validate?anchor_network=stellar:testnet" \
-H "Content-Type: application/json" \
-d @entry.json
```

## Implementation Status

- Core endpoints scaffolded and ready for extension
- Full protocol pipeline integration in progress
- Schema-aligned Codex endpoints implemented.
- Signing, anchoring, and certification endpoints will layer on shared services in future milestones.

## Role in Lockb0x Protocol

Expand Down
Loading
Loading