Skip to content
Draft
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
229 changes: 210 additions & 19 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ hyper-tls = "*"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum = "*"
strum_macros = "*"
json-patch = "*"

# diesel `uuidv07` feature is bound tight with `uuid` v0.7.x
Expand All @@ -45,6 +47,10 @@ sha3 = "0.10" # Keccak256
base64 = "0.13"
hex = "0.4"
hex-literal = "0.3"
ecdsa = "*"
p256 = "*"
sha2 = "0.10.8"


# arweave
arweave-rs = "0.1.2"
Expand Down
73 changes: 62 additions & 11 deletions docs/api.apib
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ FORMAT: 1A

# Changelog

- <2023-11-01 Wed> :: Add subkey mechanism
- <2022-02-28 Mon> :: init

# General
Expand All @@ -22,6 +23,52 @@ For example,
for JavaScript, [json-patch](https://github.com/idubrov/json-patch)
for Rust.

## About subkey signing

From <DATE_TO_BE_ANNOUNCED>, you can upload data with signature
generated by a subkey which is already binded to an avatar on
ProofService.

Notice: for subkey with `algorithm: es256` and `algorithm: rs256`, you
should `sha256` the `sign_payload` given by `POST /v1/kv/payload`
first, sign the sha256 digest, then submit to `POST /v1/kv`.

### About subkey public key format

- for `algorighm: "rs256"` : TODO
- for `algorighm: "es256"` : `0xX_CONCAT_Y_64BYTES_HEXSTRING`
- for `algorighm: "secp256k1"` : `0xCOMPRESSED_PUBKEY_HEXSTRING` or `0xUNCOMPRESSED_PUBKEY_HEXSTRING`

### Format of `signature` field:

- for `algorighm: "rs256"` : TODO
- for `algorighm: "es256"` : `0xR_CONCAT_S`
- for `algorighm: "secp256k1"` : `0xR_CONCAT_S_CONCAT_V`

### Extra: CBOR encoded signature struct

Response after calling `await navigator.credentials.get()` should be like:

```js
{

authenticatorData: Uint8Array,
// Literal string of clientData, see below
clientDataJSON: Uint8Array,
signature: Uint8Array,
}

// Struct of clientDataJSON:
// After JSON.parse(response.clientDataJSON.toString())
{
"type": "webauthn.get",
// base64.encode(sha256(sign_payload))
"challenge": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"origin": "http://localhost:3000",
"crossOrigin": false
}
```

# Group KV

## Get current KV of a persona [GET /v1/kv]
Expand All @@ -30,7 +77,6 @@ for Rust.

+ Parameters

- persona (string, required) - Deprecated. Use `avatar` instead.
- avatar (string, required) - Persona public key (hexstring started with `0x`).

+ Example
Expand All @@ -41,7 +87,6 @@ for Rust.

+ Attributes (object)

+ persona (string, required) - Deprecated. Use `avatar` instead.
+ avatar (string, required) - Avatar public key (uncompressed hexstring started with `0x`).
+ proofs (array[object], required) - All proofs belong to this persona
+ platform (string, required) - Platform (incl. `nextid`, which means public key itself).
Expand Down Expand Up @@ -119,14 +164,19 @@ Avatar not found (no KV was ever created).

> Make sure to save order-aware struct in `[]` value.

> Notice: following field groups should and only appear once.
> - For avatar sign flow: `avatar`, `platform`, `identity`
> - For subkey sign flow: `algorithm`, `public_key`

+ Request (application/json)

+ Attributes (object)

+ persona (string, required) - Deprecated. Use `avatar` instead.
+ avatar (string, required) - Avatar public key (both comressed / uncompressed and with/without `0x` are OK).
+ platform (string, required) - Platform (incl. `nextid`, which means public key itself).
+ identity (string, required) - Identity.
+ avatar (string, optional) - For Avatar sign flow: Avatar public key (both comressed / uncompressed and with/without `0x` are OK).
+ algorithm (string, optional) - For subkey sign flow: Algorithm of this subkey (now supporting `es256` and `secp256k1`)
+ public_key (string, optional) - For subkey sign flow: Public key of this subkey. See description above for format.
+ platform (string, optional) - Platform (incl. `nextid`, which means public key itself).
+ identity (string, optional) - Identity.
+ patch (object, required) - Patch to current data

+ Body
Expand Down Expand Up @@ -163,16 +213,17 @@ Avatar not found (no KV was ever created).
"sign_payload": "{\"action\":\"kv\",\"created_at\":1646983606,\"patch\":{\"a\":\"sample\",\"key_to_delete\":null,\"structure\":[\"it\",\"could\",\"be\",\"anything\"],\"this\":\"is\"},\"prev\":null,\"uuid\":\"40c13c92-31e5-40d1-aebb-143d8e5b9c5e\"}"
}

## Update a full set of key-value pairs [POST /v1/kv]
## Update a set of key-value pairs [POST /v1/kv]

+ Request (application/json)

+ Attributes (object)

+ persona (string, required) - Deprecated. Use `avatar` instead.
+ avatar (string, required) - Avatar public key.
+ platform (string, required) - Platform (incl. `nextid`, which means public key itself).
+ identity (string, required) - Identity.
+ avatar (string, optional) - for Avatar sign flow: Avatar public key.
+ algorithm (string, optional) - For subkey sign flow: Algorithm of this subkey (now supporting `es256` and `secp256k1`)
+ public_key (string, optional) - For subkey sign flow: Public key of this subkey. See description above for format.
+ platform (string, optional) - Platform (incl. `nextid`, which means public key itself).
+ identity (string, optional) - Identity.
+ uuid (string, required) - UUID generated by server in `POST /v1/kv/payload`.
+ created_at (number, required) - Creation timestamp generated by server in `POST /v1/kv/payload`.
+ signature (string, required) - Signature of this request. Base64-ed.
Expand Down
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@
{
# defaultPackage = naersk-lib.buildPackage ./.;
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [ cargo rustc rustfmt rust-analyzer pre-commit rustPackages.clippy pkg-config openssl postgresql ];
buildInputs = with pkgs; [
cargo
rustc
rustfmt
rust-analyzer
pre-commit
rustPackages.clippy
pkg-config
openssl
postgresql
diesel-cli
];
RUST_SRC_PATH = pkgs.rustPlatform.rustLibSrc;
};
});
Expand Down
63 changes: 49 additions & 14 deletions src/controller/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ use crate::{
crypto::secp256k1::Secp256k1KeyPair,
error::Error,
model::{establish_connection, kv_chains::NewKVChain},
proof_client::can_set_kv,
proof_client::{can_set_kv, self},
};
use http::StatusCode;
use serde::{Deserialize, Serialize};

use super::error_response;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct PayloadRequest {
pub persona: Option<String>,
pub avatar: Option<String>,
pub algorithm: Option<crate::types::subkey::Algorithm>,
pub public_key: Option<String>,
pub platform: String,
pub identity: String,
pub patch: serde_json::Value,
Expand All @@ -26,20 +29,50 @@ struct PayloadResponse {

pub async fn controller(req: Request) -> Result<Response, Error> {
let params: PayloadRequest = json_parse_body(&req)?;
if params.avatar.is_some() {
sign_payload_with_avatar(&params).await
} else if params.algorithm.is_some() && params.public_key.is_some() {
sign_payload_with_subkey(&params).await
} else {
Ok(error_response(
Error::ParamError("(avatar) or (algorithm, public_key) is not provided".into())
))
}
}

let keypair = Secp256k1KeyPair::from_pubkey_hex(
&params
.avatar
.or(params.persona)
.ok_or_else(|| Error::ParamError("avatar not found".into()))?,
)?;
async fn sign_payload_with_avatar(params: &PayloadRequest) -> Result<Response, Error> {
let keypair = Secp256k1KeyPair::from_pubkey_hex(params.avatar.as_ref().unwrap())?;
can_set_kv(&keypair.public_key, &params.platform, &params.identity).await?;
let mut conn = establish_connection();
let mut new_kvchain = NewKVChain::for_persona(&mut conn, &keypair.public_key)?;

new_kvchain.platform = params.platform;
new_kvchain.identity = params.identity;
new_kvchain.patch = params.patch;
new_kvchain.platform = params.platform.clone();
new_kvchain.identity = params.identity.clone();
new_kvchain.patch = params.patch.clone();
let sign_payload = new_kvchain.generate_signature_payload()?;

Ok(json_response(
StatusCode::OK,
&PayloadResponse {
sign_payload: serde_json::to_string(&sign_payload)?,
uuid: sign_payload.uuid.to_string(),
created_at: sign_payload.created_at,
},
)?)
}

async fn sign_payload_with_subkey(params: &PayloadRequest) -> Result<Response, Error> {
let algorithm = params.algorithm.as_ref().unwrap();
let public_key = params.public_key.as_ref().unwrap();
let subkey = proof_client::find_subkey(&algorithm, &public_key).await?;
let avatar = Secp256k1KeyPair::from_pubkey_hex(&subkey.avatar)?;
can_set_kv(&avatar.public_key, &params.platform, &params.identity).await?;

let mut conn = establish_connection();
let mut new_kvchain = NewKVChain::for_persona(&mut conn, &avatar.public_key)?;
new_kvchain.platform = params.platform.clone();
new_kvchain.identity = params.identity.clone();
new_kvchain.patch = params.patch.clone();
let sign_payload = new_kvchain.generate_signature_payload()?;

Ok(json_response(
Expand Down Expand Up @@ -99,8 +132,9 @@ mod tests {
} = Secp256k1KeyPair::generate();

let req_body = PayloadRequest {
persona: Some(compress_public_key(&public_key)),
avatar: None,
avatar: Some(compress_public_key(&public_key)),
algorithm: None,
public_key: None,
platform: "facebook".into(),
identity: Faker.fake(),
patch: json!({"test":"abc"}),
Expand Down Expand Up @@ -131,8 +165,9 @@ mod tests {
let old_kv_chain = generate_data(&mut conn, &public_key).unwrap();

let req_body = PayloadRequest {
persona: None,
avatar: Some(compress_public_key(&public_key)),
algorithm: None,
public_key: None,
platform: "facebook".into(),
identity: Faker.fake(),
patch: json!({"test":"abc"}),
Expand Down
Loading