diff --git a/data/0e972deae436609ece1bf2aa80232fb6cf1b386bef5ed59cc0ee2abbf3de5cc7.json b/data/0e972deae436609ece1bf2aa80232fb6cf1b386bef5ed59cc0ee2abbf3de5cc7.json new file mode 100644 index 0000000..a3e0bcc --- /dev/null +++ b/data/0e972deae436609ece1bf2aa80232fb6cf1b386bef5ed59cc0ee2abbf3de5cc7.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABQEg45x7MvYhHfhpEJeFV1ftnO/Oorhy/5W5sX5YYLSVStoAINBKsjJ0K7SrOhNovUYV5ObQIkq3GgFrr4UgozLJd4c3AfzsutQGECa+hvDDQGf4726neI5ispE=","signature":"zGL6tBvs5fO2Neur/nwV5AjhZGNquheAdtqTahBMvt44IekpPUuXdHqaiDNu/aPhClO7C4ijVlxxj98iY9MYAg==","object_id":"0e972deae436609ece1bf2aa80232fb6cf1b386bef5ed59cc0ee2abbf3de5cc7"} diff --git a/data/102ae6a0161833426efb7483cf7a2ff3c30f52b8c865520a136530566c33f823.json b/data/102ae6a0161833426efb7483cf7a2ff3c30f52b8c865520a136530566c33f823.json new file mode 100644 index 0000000..f604ab6 --- /dev/null +++ b/data/102ae6a0161833426efb7483cf7a2ff3c30f52b8c865520a136530566c33f823.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAIOcA8DdWUiDbQo+nTu3ef0WKzCqMGhiXMYY2Sf5TT/koACCRvis7oAKfVr+F4lfOGDXOzB/0mI1pmhUeVjYrhlxaCARhdXRoAAx3cyBsaXZlIHRlc3SApKfaBrykp9oGEAcHBwcHBwcHBwcHBwcHBwc=","signature":"lnBnOW9Q5VuOtcCnxi1JXtRKV9z4Q86IouxwASclUzLRTdIsJ0evxq+/dBmIdt/6Hdxr5213UIGQVVn5HMkaCw==","object_id":"102ae6a0161833426efb7483cf7a2ff3c30f52b8c865520a136530566c33f823"} diff --git a/data/1ab38c6aa921fb5eaaba099479341811e37afd7273d09d2e39c90192c532ee4c.json b/data/1ab38c6aa921fb5eaaba099479341811e37afd7273d09d2e39c90192c532ee4c.json new file mode 100644 index 0000000..ebcb6fb --- /dev/null +++ b/data/1ab38c6aa921fb5eaaba099479341811e37afd7273d09d2e39c90192c532ee4c.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAIILLyye2RTa7ii04SOM8iyDZw+veAHiwLX9LNytKirMKACDQSrIydCu0qzoTaL1GFeTm0CJKtxoBa6+FIKMyyXeHNwVsb2dpbgEHc2Vzc2lvbgMWS2pfWThqNVFCaUNFRmREUWd6QUtyQRdTaWduIGluIHRvIGRlbW8gc2VydmljZfbnutQGsui61AYQKB2UGcgF4ZNwWHdSmG0Etw==","signature":"3agPqlkbvKBpcIKf2fCz1QJxDPzLghlT583J/HTvcbpJesNkLtLWBmqoVopoqjW1qZcAyVkmShVSGYa1NTsKAA==","object_id":"1ab38c6aa921fb5eaaba099479341811e37afd7273d09d2e39c90192c532ee4c"} diff --git a/data/2745e5cba6f8c208f58e3eaac50c07ed871bd3977223767c1f8adf831467bbc7.json b/data/2745e5cba6f8c208f58e3eaac50c07ed871bd3977223767c1f8adf831467bbc7.json new file mode 100644 index 0000000..2826ec5 --- /dev/null +++ b/data/2745e5cba6f8c208f58e3eaac50c07ed871bd3977223767c1f8adf831467bbc7.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAILOiOd/2wrH9Xd8KeVuTO6jprESVzIQdrry1RhALhETeACB/2ppLTnSq9hP6ZRY4K4olAOg94xHdULPB24QQh/PWvwRhdXRoABRhbmRyb2lkIHdzIGxpdmUgdGVzdICkp9oGvKSn2gYQBwcHBwcHBwcHBwcHBwcHBw==","signature":"0A62EzU0s8+MfWa+GPyd+J80xprG4orCajBD2rnupU5uet41luCCzFR3cB+TtzlE+Rrh+DrBq4tI3kpCLUUbAA==","object_id":"2745e5cba6f8c208f58e3eaac50c07ed871bd3977223767c1f8adf831467bbc7"} diff --git a/data/618ff3cb6713ba3aecab174ca3037f714b90f5a2094323e30a94ee5abacb77bc.json b/data/618ff3cb6713ba3aecab174ca3037f714b90f5a2094323e30a94ee5abacb77bc.json new file mode 100644 index 0000000..646ceda --- /dev/null +++ b/data/618ff3cb6713ba3aecab174ca3037f714b90f5a2094323e30a94ee5abacb77bc.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAIILLyye2RTa7ii04SOM8iyDZw+veAHiwLX9LNytKirMKACDQSrIydCu0qzoTaL1GFeTm0CJKtxoBa6+FIKMyyXeHNwVsb2dpbgEHc2Vzc2lvbgMWbzY0TFpZemZROFJWUmNKUE83OUhtQRdTaWduIGluIHRvIGRlbW8gc2VydmljZe3nutQGqei61AYQY5R39BPmNKy6H7eeqdvN3w==","signature":"YOnrIHdzsIKyHrHZsNcQtSfiaKEvD6UchTOcvMOXZQm0GdJ4rEN2I71fhVpv/JP2Gt3sisuy/7MrrxFrd51AAw==","object_id":"618ff3cb6713ba3aecab174ca3037f714b90f5a2094323e30a94ee5abacb77bc"} diff --git a/data/7d59b6aba3a2681f90c5c705f3fb0ab1c53323326839408d4dd351580a8a1972.json b/data/7d59b6aba3a2681f90c5c705f3fb0ab1c53323326839408d4dd351580a8a1972.json new file mode 100644 index 0000000..875707a --- /dev/null +++ b/data/7d59b6aba3a2681f90c5c705f3fb0ab1c53323326839408d4dd351580a8a1972.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABQEgyG2kH+/FGwQZKum7uHeiaHlM5CUUEiAu/crilN27sRcAINBKsjJ0K7SrOhNovUYV5ObQIkq3GgFrr4UgozLJd4c3Af/nutQGEKrodzQXiRYoNh/K6DU7x9o=","signature":"ba3ZEGaovxTLixjkP9KiDIrJ1zHGllW6mNgA3ORF/19m/M30XgaiJXbmCKtkQshi9E4xGD5+U/dmcAmPDJO7Aw==","object_id":"7d59b6aba3a2681f90c5c705f3fb0ab1c53323326839408d4dd351580a8a1972"} diff --git a/data/816d5d2e26f12f4f4ea339d53e8dcc36478e73bb5ecce03148c57890b44668ec.json b/data/816d5d2e26f12f4f4ea339d53e8dcc36478e73bb5ecce03148c57890b44668ec.json new file mode 100644 index 0000000..0ad0f0d --- /dev/null +++ b/data/816d5d2e26f12f4f4ea339d53e8dcc36478e73bb5ecce03148c57890b44668ec.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABQEgYY/zy2cTujrsqxdMowN/cUuQ9aIJQyPjCpTuWrrLd7wAINBKsjJ0K7SrOhNovUYV5ObQIkq3GgFrr4UgozLJd4c3Ae7nutQGEC3VyacYr+5LBo0xsHtvo1Y=","signature":"czfLYKfIMlzRXr3nDa+q934ciyYA00+nL+uA0pSRN+nFNHBUo/S5sFjPehP9oHYh0VUeklbObckrerdW5BtvAg==","object_id":"816d5d2e26f12f4f4ea339d53e8dcc36478e73bb5ecce03148c57890b44668ec"} diff --git a/data/94764932c222671278b579b5cf8dbce706f71c023f9a11c32a61bf307bceab56.json b/data/94764932c222671278b579b5cf8dbce706f71c023f9a11c32a61bf307bceab56.json new file mode 100644 index 0000000..c50a2fa --- /dev/null +++ b/data/94764932c222671278b579b5cf8dbce706f71c023f9a11c32a61bf307bceab56.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEAAQEAIGgxl6UgyEP7hHyaXK+FCuY7aZMx2iIc0oJcWza8bVR2CHBvdy1saXZlgOLPqgY=","signature":"vu1W9QFDcH7R/KunZbzoJMwhm0on+UnqoqpyDd+W8AOV6gDg6L6dlTRdY6Ge/0OSl+lXufW+xgiRChrsh5y0CA==","object_id":"94764932c222671278b579b5cf8dbce706f71c023f9a11c32a61bf307bceab56"} diff --git a/data/a902de693d1ea14571eb1786d3f7e545b7bb4e8e81226d00df2548972ebd4e85.json b/data/a902de693d1ea14571eb1786d3f7e545b7bb4e8e81226d00df2548972ebd4e85.json new file mode 100644 index 0000000..fc4b2ab --- /dev/null +++ b/data/a902de693d1ea14571eb1786d3f7e545b7bb4e8e81226d00df2548972ebd4e85.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEAAQEAIIJEncifJ40xeCa/nFA2Zy/eIBTNS8P7/s2R6pNesc6YCHBvdy1saXZlgOLPqgY=","signature":"WjY2lHununQjG0NOkNPzzyStsr3IxdiOToLNRlxP2YsuRUXpc0fvKho71H12hO+/3GYHZgKkP7IQvEWmD8NpAA==","object_id":"a902de693d1ea14571eb1786d3f7e545b7bb4e8e81226d00df2548972ebd4e85"} diff --git a/data/c4a41dcadab39ba3b10d2f7749338e5c5f8c2ea4e86b589ff3067d28204ca153.json b/data/c4a41dcadab39ba3b10d2f7749338e5c5f8c2ea4e86b589ff3067d28204ca153.json new file mode 100644 index 0000000..a7ffded --- /dev/null +++ b/data/c4a41dcadab39ba3b10d2f7749338e5c5f8c2ea4e86b589ff3067d28204ca153.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAIILLyye2RTa7ii04SOM8iyDZw+veAHiwLX9LNytKirMKACCRvis7oAKfVr+F4lfOGDXOzB/0mI1pmhUeVjYrhlxaCAVsb2dpbgEHc2Vzc2lvbgMWTDlCTm9YaEVwcWd6bnhOUFl1UUhyQRdTaWduIGluIHRvIGRlbW8gc2VydmljZc3nutQGiei61AYQMbmTvcU0ct/YQH5qLSU48w==","signature":"mJigLGS2gykFOEQ/hvfo40Kca79ZNIgv8GtY2bWKU9gjJ+pjpjd3TgGd5R/ss++QNoROBcIqP/Ndpgiw5WX9Bw==","object_id":"c4a41dcadab39ba3b10d2f7749338e5c5f8c2ea4e86b589ff3067d28204ca153"} diff --git a/data/c86da41fefc51b04192ae9bbb877a268794ce4251412202efdcae294ddbbb117.json b/data/c86da41fefc51b04192ae9bbb877a268794ce4251412202efdcae294ddbbb117.json new file mode 100644 index 0000000..4dd1af3 --- /dev/null +++ b/data/c86da41fefc51b04192ae9bbb877a268794ce4251412202efdcae294ddbbb117.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAIILLyye2RTa7ii04SOM8iyDZw+veAHiwLX9LNytKirMKACDQSrIydCu0qzoTaL1GFeTm0CJKtxoBa6+FIKMyyXeHNwVsb2dpbgEHc2Vzc2lvbgMWeS04ak10enVvTnBUelluLVpmbmRPURdTaWduIGluIHRvIGRlbW8gc2VydmljZf/nutQGu+i61AYQZGJ4M7FvkiCnoTb/TFSwtw==","signature":"wVgK0dZ9qSBhimQzELmfdd52DYrK3nwP6Rv+ZNDHgrSKI4Kapww07O0E8BNRVhXagrZdMPtpjbSNJDEAhhDLDg==","object_id":"c86da41fefc51b04192ae9bbb877a268794ce4251412202efdcae294ddbbb117"} diff --git a/data/checkpoint-1.json b/data/checkpoint-1.json new file mode 100644 index 0000000..a04763c --- /dev/null +++ b/data/checkpoint-1.json @@ -0,0 +1 @@ +{"bytes":"dHJ1c3QubjFrby5kZXYvY2twdC8xAAEBASCa4Wwr3EzcvPeg2HpkwiKrsJdV9xapyWTyt1Iuis+NpSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAODst9QG","signature":"cANWA+qCI4aANYhUpsN0cwgtnOb8sgGBcphmydAG/XJYQnWCbOtRLeInsf6cHxt64Oms7962/Zvdgs4mjxn/Ag==","id":"29080de9e64a75de4453a96f5c4294bcc3b3d81c4402a664ebb3a2f94e657c33"} \ No newline at end of file diff --git a/data/checkpoint-2.json b/data/checkpoint-2.json new file mode 100644 index 0000000..7218fd3 --- /dev/null +++ b/data/checkpoint-2.json @@ -0,0 +1 @@ +{"bytes":"dHJ1c3QubjFrby5kZXYvY2twdC8xAAECAiAO+gsIekCmIn1LaLOZVZdaK2JWBBKupWpvU4CIAkLEayApCA3p5kp13kRTqW9cQpS8w7PYHEQCpmTrs6L5TmV8M7f0t9QG","signature":"Rx9wLAo5NFvv3NNuOzXyB3MwUWU3u3/uLHKlHlraz+bxCVVI+So6Cvh5MVNvjBDf92Sg6Np+5Lb2NMZaNp7JDQ==","id":"6ae384a485308701adf6f0da2767266ff90f0c25b024ba706fdd1c60ca010f6c"} \ No newline at end of file diff --git a/data/checkpoint_meta.json b/data/checkpoint_meta.json new file mode 100644 index 0000000..7d2783a --- /dev/null +++ b/data/checkpoint_meta.json @@ -0,0 +1 @@ +{"epoch":2,"last_hash":"6ae384a485308701adf6f0da2767266ff90f0c25b024ba706fdd1c60ca010f6c"} \ No newline at end of file diff --git a/data/d8a462d2755ffb16d8bc615e78db232289f571988b158febf190f71a969153d8.json b/data/d8a462d2755ffb16d8bc615e78db232289f571988b158febf190f71a969153d8.json new file mode 100644 index 0000000..35a46b6 --- /dev/null +++ b/data/d8a462d2755ffb16d8bc615e78db232289f571988b158febf190f71a969153d8.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAIPMtJyRnRoKrwG4/7YYYNg+Q/X358s7n7K4cPkcz3PtjACDVXkf9iQ8Zx+mWZW8YMFy/f/Xk6TpYsr5C0v8ldWigUwRhdXRoAAx3cyBsaXZlIHRlc3SApKfaBrykp9oGEAcHBwcHBwcHBwcHBwcHBwc=","signature":"EF6/T3cGoF4HJ47yGM9QfMB1eilnvbrSg8vtqupLF2f+zOO1fflYKmQMXBi8RJyrX/pJTMBdZspH3sJkQcFiAw==","object_id":"d8a462d2755ffb16d8bc615e78db232289f571988b158febf190f71a969153d8"} diff --git a/data/e39c7b32f6211df8691097855757ed9cefcea2b872ff95b9b17e5860b4954ada.json b/data/e39c7b32f6211df8691097855757ed9cefcea2b872ff95b9b17e5860b4954ada.json new file mode 100644 index 0000000..e9b1ce0 --- /dev/null +++ b/data/e39c7b32f6211df8691097855757ed9cefcea2b872ff95b9b17e5860b4954ada.json @@ -0,0 +1 @@ +{"tce":"dHJ1c3QubjFrby5kZXYvdGNlLzEABAEAILZvMfVh6H5uBBpoA570y5ZKs5TXNc6U5U0ff9wVJfltACDQSrIydCu0qzoTaL1GFeTm0CJKtxoBa6+FIKMyyXeHNwVsb2dpbgEHc2Vzc2lvbgMWVHBqTDNFZkdhS20wczZ0TXpQQTU4ZxdTaWduIGluIHRvIGRlbW8gc2VydmljZfzsutQGuO261AYQA8GUwWqDoGyqy9Y0E83RhQ==","signature":"wG7vho7wxIMqO7205aFzbhAtpkHvRSHhrLvs+iKYyk51MNZZZgCOF4LFk2hFv3SiOkxRnm5rcWwgjyqGK5AcDA==","object_id":"e39c7b32f6211df8691097855757ed9cefcea2b872ff95b9b17e5860b4954ada"} diff --git a/data/relay_key.seed b/data/relay_key.seed new file mode 100644 index 0000000..c7687a1 --- /dev/null +++ b/data/relay_key.seed @@ -0,0 +1,2 @@ +ɬ3 +6;kj9hLR\}m7 \ No newline at end of file diff --git a/docs/SERVICE-GUIDE.md b/docs/SERVICE-GUIDE.md new file mode 100644 index 0000000..8c25a01 --- /dev/null +++ b/docs/SERVICE-GUIDE.md @@ -0,0 +1,121 @@ +# SERVICE-GUIDE.md — вход по Niko Trust в вашем сервисе + +Рецепт для владельца сервиса: как принимать Niko Trust как основной способ +аутентификации. Полный рабочий код — [`examples/service`](../examples/service) +(сервис) и [`examples/approve`](../examples/approve) (роль кошелька). + +## Модель в двух абзацах + +Идентичность — это пара ключей Ed25519; адрес `trust1…` выведен из публичного +ключа (bech32). Сервис не видит паролей: он публикует **ApprovalRequest** — +подписанный вопрос «войти?» адресованный конкретному адресу, действительный +≤60 секунд. Владелец кошелька (приложение niko_trust_gui / Android) отвечает +**ApprovalResponse**, который криптографически привязан к байтам именно этого +запроса (INV-4). Релей — тупое хранилище конвертов: он не проверяет подписи и +не знает, кто прав; всю верификацию сервис делает сам локально. + +Что это даёт по безопасности: фишинг невозможен (запрос подписан ключом +сервиса и показывается в кошельке с его адресом), повтор невозможен (ответ +умирает вместе с окном запроса), подмена решения невозможна (ответ подписан +ключом пользователя и привязан к хешу запроса), а компрометация релея не даёт +атакующему ничего, кроме отказа в обслуживании. + +## Поток логина + +``` +браузер ваш сервис релей кошелёк + | GET /login?user=X | | | + |----------------------->| ApprovalRequest(action= | | + | | "login", recipient=X) | | + | |-- POST /v1/objects ------->| | + |<- { id } --------------| |--- push ----->| + | | GET /v1/responses?request=id (или WS) | + | ...пользователь жмёт «Разрешить» в кошельке... |<-- store -----| + | GET /login/status |<-- envelope {tce,sig} -----| | + |----------------------->| VerifyApprovalResponse() | | + |<- {approved, user:X} --| → своя сессия | | +``` + +## Шаги + +### 1. Идентичность сервиса + +Сгенерируйте один раз ключ и храните сид как секрет (это «сертификат» +сервиса): + +```go +sv, _ := signer.Generate() +fmt.Println(sv.Address(), hex.EncodeToString(sv.Seed())) +// при старте: signer.FromSeed(seedBytes) +``` + +Адрес сервиса увидит пользователь в кошельке рядом с текстом запроса. + +### 2. Создание запроса на вход + +```go +req := &protocol.ApprovalRequest{ + Sender: sv.Public(), + Recipient: []byte(userAddr.PubKey()), // чей это вход + Action: "login", + Payload: map[string]tce.Value{"session": tce.String(sessionID)}, + Message: "Sign in to demo service", // человек это прочитает + CreatedAt: uint64(time.Now().Unix()), + ExpiresAt: uint64(time.Now().Unix() + 60), + Nonce: nonce16(), +} +tceBytes, _ := protocol.EncodeApprovalRequest(req) +sig := sv.Sign(tceBytes) +id := tce.ComputeID(tceBytes) // = object id, им же ссылается ответ +POST {base}/v1/objects {"tce": b64(tceBytes), "signature": b64(sig)} +``` + +`Recipient` обязан быть валидным адресом: запрос всегда адресный. + +### 3. Ожидание ответа + +Два способа: + +- **Опрос** (просто): `GET /v1/responses?request=` каждые ~0.7 c. + Требует сессию чтения — см. шаг 5. +- **WebSocket** (правильно): подключиться к `GET /v1/ws` со своим токеном и + отправить `{"op":"subscribe","channel":"responses","key":""}` — + события приходят мгновенно (docs/API.md §WebSocket). + +### 4. Верификация — единственная критичная строка + +```go +resp, err := protocol.VerifyApprovalResponse(reqTCE, reqSig, respTCE, respSig) +if err == nil && resp.Decision == protocol.Allow && + bytes.Equal(resp.Responder, []byte(userAddr.PubKey())) { + // адрес верифицирован → выпустить свою сессию +} +``` + +Проверка уже включает: строгий декод обоих объектов, подпись под ключом +ответившего, привязку к точным байтам вашего запроса и окно времени. +Сверка `Responder` с ожидаемым адресом отсекает ответы чужих кошельков. + +### 5. Сессия чтения релея + +Чтение лент требует токена: `POST /v1/auth/challenge` → подписать +`AuthAssertion` (audience взять из `GET /v1/config`) → `POST /v1/auth/assert` +→ `session_token`, живёт 30 минут (docs/API.md). Готовые реализации: +`pkg/protocol.VerifyAuthAssertion`-клиенты в примерах выше. + +## Где брать готовый клиентский код + +| Язык | Пакет | +|-----------|-----------------------------------------------------------------------| +| Go | этот модуль: `pkg/tce`, `pkg/protocol`, `pkg/verify`, `pkg/address`, `pkg/identity/signer` | +| Rust | крейт `niko_trust_gui` (git-зависимость), модули `tce`, `protocol`, `relay`; пример `examples/service_login.rs` | +| Kotlin/JVM| модуль `:sdk` репозитория niko_trust_android (`RelayClient`, `LiveFeed`, протокол) | + +## Эксплуатация + +- Релей за обратным прокси? Включите `trust_proxy: true` в config.yaml, иначе + рейт-лимиты считают всех одним IP. +- Рестарт релея сбрасывает сессии — клиенты перекладываются сами; ваш сервис + должен переживать 502/401 во время окна рестарта. +- Храните сид сервиса как секрет: он подписывает все запросы, и пользователь + видит его адрес в каждом подтверждении. diff --git a/examples/approve/main.go b/examples/approve/main.go new file mode 100644 index 0000000..67f0a55 --- /dev/null +++ b/examples/approve/main.go @@ -0,0 +1,131 @@ +// Command approve acts as the user's wallet for the demo: it fetches an +// ApprovalRequest from the relay by id, checks it is addressed to us, signs +// an ApprovalResponse and stores it. Combined with examples/service this +// exercises a full login round trip on one machine. +package main + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" + + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" +) + +var ( + relayURL = flag.String("relay", "http://127.0.0.1:8080", "relay base URL") + idHex = flag.String("id", "", "request id (hex) to answer") + deny = flag.Bool("deny", false, "deny instead of allow") + seedHex = flag.String("seed", "", "user identity seed (hex); generated when empty") +) + +func main() { + flag.Parse() + if *idHex == "" { + log.Fatal("-id is required") + } + + var user *signer.Signer + var err error + if *seedHex == "" { + user, err = signer.Generate() + } else { + b, e := hex.DecodeString(*seedHex) + if e != nil || len(b) != ed25519SeedSize { + log.Fatal("-seed must be 64 hex characters") + } + user, err = signer.FromSeed(b) + } + if err != nil { + log.Fatal(err) + } + fmt.Println("wallet identity:", user.Address()) + + // Fetch the exact request bytes by their content address. A hostile + // relay can substitute anything here — that is why we verify instead of + // just decoding. + reqTCE, reqSig := getObject(*relayURL, *idHex) + req, err := protocol.VerifyApprovalRequest(reqTCE, reqSig) + if err != nil { + log.Fatalf("request does not verify: %v", err) + } + if string(req.Recipient) != string(user.Public()) { + log.Fatal("this request is not addressed to our identity") + } + fmt.Printf("action=%q message=%q\n", req.Action, req.Message) + + decision := protocol.Allow + if *deny { + decision = protocol.Deny + } + resp := &protocol.ApprovalResponse{ + RequestHash: tce.ComputeID(reqTCE), // INV-4: commits to the exact bytes + Responder: user.Public(), + Decision: decision, + CreatedAt: uint64(time.Now().Unix()), + Nonce: nonce(), + } + respTCE, err := protocol.EncodeApprovalResponse(resp) + if err != nil { + log.Fatal(err) + } + out := post(*relayURL+"/v1/objects", + fmt.Sprintf(`{"tce":%q,"signature":%q}`, + base64.StdEncoding.EncodeToString(respTCE), + base64.StdEncoding.EncodeToString(user.Sign(respTCE)))) + fmt.Printf("response stored: %s\n", strings.TrimSpace(out)) +} + +// ------------------------------------------------------------- tiny http io + +func getObject(base, id string) ([]byte, []byte) { + httpResp, err := http.Get(base + "/v1/objects/" + id) + if err != nil { + log.Fatal(err) + } + defer httpResp.Body.Close() + raw, _ := io.ReadAll(httpResp.Body) + var env struct { + Tce string `json:"tce"` + Signature string `json:"signature"` + Error string `json:"error"` + } + json.Unmarshal(raw, &env) + if env.Tce == "" { + log.Fatalf("fetch object %s: %s", id, env.Error) + } + tceB, e1 := base64.StdEncoding.DecodeString(env.Tce) + sigB, e2 := base64.StdEncoding.DecodeString(env.Signature) + if e1 != nil || e2 != nil { + log.Fatal("bad envelope encoding") + } + return tceB, sigB +} + +func post(url, body string) string { + httpResp, err := http.Post(url, "application/json", strings.NewReader(body)) + if err != nil { + log.Fatal(err) + } + defer httpResp.Body.Close() + b, _ := io.ReadAll(httpResp.Body) + return string(b) +} + +func nonce() []byte { + n := make([]byte, tce.NonceSize) + rand.Read(n) + return n +} + +const ed25519SeedSize = 32 diff --git a/examples/service/main.go b/examples/service/main.go new file mode 100644 index 0000000..c279753 --- /dev/null +++ b/examples/service/main.go @@ -0,0 +1,362 @@ +// Command service is a complete minimal "log in with Niko Trust" service. +// +// Flow (docs/SERVICE-GUIDE.md): +// +// GET /login -> mint an ApprovalRequest, store it on the relay, +// return { id } and start watching for a response +// GET /login/status -> pending | approved
| denied | expired +// +// The user sees the request in their wallet app and taps approve; the relay +// stores the signed ApprovalResponse; this service fetches it back and +// verifies it locally against its own request bytes — nothing about the +// decision is taken on trust. +// +// Try it end-to-end: +// +// go run ./examples/service -relay http://127.0.0.1:8080 -http 127.0.0.1:9090 & +// curl -s localhost:9090/login # -> {"id": "..."} +// go run ./examples/approve -relay http://127.0.0.1:8080 \ +// -id # act as the user +package main + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "strings" + "sync" + "time" + + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" +) + +var ( + relayURL = flag.String("relay", "http://127.0.0.1:8080", "relay base URL") + httpAddr = flag.String("http", "127.0.0.1:9090", "listen address of this demo service") + seedHex = flag.String("seed", "", "service identity seed (hex, 64 chars); generated when empty") +) + +type pending struct { + req *protocol.ApprovalRequest + reqTCE []byte + reqSig []byte + result chan string // user address once approved; "" on deny/expire + deadline time.Time +} + +type service struct { + signer *signer.Signer + token string // relay session for read endpoints + pending sync.Map // request id hex -> *pending +} + +// authenticate performs the challenge/assert handshake (docs/API.md) and +// returns a session token with read access. +func authenticate(base string, sv *signer.Signer) string { + cfg := getJSON(base + "/v1/config") + ch := postJSON(base+"/v1/auth/challenge", "{}") + chal, err := hex.DecodeString(ch["challenge"].(string)) + if err != nil || len(chal) != tce.ChallengeSize { + log.Fatal("bad challenge from relay") + } + a := &protocol.AuthAssertion{ + PubKey: sv.Public(), + Challenge: chal, + Scope: "read", + Audience: cfg["audience"].(string), + CreatedAt: uint64(time.Now().Unix()), + } + aTCE, err := protocol.EncodeAuthAssertion(a) + if err != nil { + log.Fatal(err) + } + body := fmt.Sprintf(`{"tce":%q,"signature":%q}`, + base64.StdEncoding.EncodeToString(aTCE), + base64.StdEncoding.EncodeToString(sv.Sign(aTCE))) + out := post(base+"/v1/auth/assert", body) + var doc struct { + SessionToken string `json:"session_token"` + } + json.Unmarshal([]byte(out), &doc) + if doc.SessionToken == "" { + log.Fatalf("auth assert failed: %s", out) + } + return doc.SessionToken +} + +func main() { + flag.Parse() + + var sv *signer.Signer + var err error + if *seedHex == "" { + sv, err = signer.Generate() + } else { + b, e := hex.DecodeString(*seedHex) + if e != nil || len(b) != ed25519.SeedSize { + log.Fatal("-seed must be exactly 64 hex characters") + } + sv, err = signer.FromSeed(b) + } + if err != nil { + log.Fatal(err) + } + log.Printf("service identity: %s", sv.Address()) + + token := authenticate(*relayURL, sv) + log.Printf("relay session established") + + svc := &service{signer: sv, token: token} + mux := http.NewServeMux() + mux.HandleFunc("GET /login", svc.startLogin) + mux.HandleFunc("GET /login/status", svc.loginStatus) + log.Printf("listening on http://%s", *httpAddr) + log.Fatal(http.ListenAndServe(*httpAddr, mux)) +} + +// startLogin mints a fresh login challenge addressed to one user: +// GET /login?user=trust1... +// +// Only the holder of that identity can produce a valid ApprovalResponse, so +// a verified "approved" reply is proof the account owner consents — exactly +// the property a passwordless login needs. +func (s *service) startLogin(w http.ResponseWriter, r *http.Request) { + user, err := address.Parse(r.URL.Query().Get("user")) + if err != nil { + httpError(w, 400, fmt.Errorf("missing or bad ?user=")) + return + } + now := time.Now().Unix() + session := make([]byte, 16) + if _, err := rand.Read(session); err != nil { + httpError(w, 500, err) + return + } + + req := &protocol.ApprovalRequest{ + Sender: s.signer.Public(), + Recipient: []byte(user.PubKey()), + Action: "login", + Payload: map[string]tce.Value{ + "session": tce.String(base64.RawURLEncoding.EncodeToString(session)), + }, + Message: "Sign in to demo service", + CreatedAt: uint64(now), + ExpiresAt: uint64(now + 60), // protocol caps approval windows at 60 s + Nonce: nonce(), + } + reqTCE, err := protocol.EncodeApprovalRequest(req) + if err != nil { + httpError(w, 500, err) + return + } + reqSig := s.signer.Sign(reqTCE) + + _, code, body := postEnvelope(*relayURL, reqTCE, reqSig) + if code != 200 { + httpError(w, 502, fmt.Errorf("relay store: %d %s", code, body)) + return + } + + reqID := tce.ComputeID(reqTCE) + id := hex.EncodeToString(reqID[:]) + s.pending.Store(id, &pending{ + req: req, + reqTCE: reqTCE, + reqSig: reqSig, + result: make(chan string, 1), + deadline: time.Now().Add(70 * time.Second), + }) + go s.watch(id) + + writeJSON(w, map[string]string{"id": id}) +} + +// watch polls the relay until the request is answered or expires. A +// production service would subscribe to the `responses` WebSocket channel of +// the relay instead of polling (docs/API.md §WebSocket). +func (s *service) watch(id string) { + v, _ := s.pending.Load(id) + p := v.(*pending) + for time.Now().Before(p.deadline) { + time.Sleep(700 * time.Millisecond) + tceB64, sigB64, ok := fetchResponse(*relayURL, s.token, id) + if !ok { + continue + } + respTCE, e1 := base64.StdEncoding.DecodeString(tceB64) + respSig, e2 := base64.StdEncoding.DecodeString(sigB64) + if e1 != nil || e2 != nil { + continue + } + + // The whole security model lives in this one call: strict decode of + // both objects, signature under the responder key, binding to our + // exact request bytes. + resp, err := protocol.VerifyApprovalResponse(p.reqTCE, p.reqSig, respTCE, respSig) + if err != nil { + continue // hostile or mismatched envelope: keep waiting + } + if !bytes.Equal(resp.Responder, p.req.Recipient) { + continue // someone else answered; not our user + } + user, err := address.FromPubKey(resp.Responder) + if err != nil { + continue + } + answer := "" + if resp.Decision == protocol.Allow { + answer = user.String() + } + select { + case p.result <- answer: + default: + } + return + } + select { + case p.result <- "": + default: + } +} + +// loginStatus reports the outcome for one login attempt. +func (s *service) loginStatus(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + v, ok := s.pending.Load(id) + if !ok { + httpError(w, 404, fmt.Errorf("unknown id")) + return + } + p := v.(*pending) + select { + case user := <-p.result: + if user == "" { + writeJSON(w, map[string]string{"status": "denied"}) + return + } + // A real service would issue its own session cookie bound to `user` + // here. The verified address IS the identity; nothing else is needed. + writeJSON(w, map[string]string{"status": "approved", "user": user}) + default: + if time.Now().After(p.deadline) { + writeJSON(w, map[string]string{"status": "expired"}) + return + } + writeJSON(w, map[string]string{"status": "pending"}) + } +} + +// ------------------------------------------------------------- http helpers + +func writeJSON(w http.ResponseWriter, v any) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(v) +} + +func httpError(w http.ResponseWriter, code int, err error) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) +} + +// ---------------------------------------------------------------- transport + +// postObject stores an envelope; returns object id, status, body. +func postEnvelope(base string, tceBytes, sig []byte) (string, int, string) { + body := fmt.Sprintf(`{"tce":%q,"signature":%q}`, + base64.StdEncoding.EncodeToString(tceBytes), + base64.StdEncoding.EncodeToString(sig)) + resp, err := http.Post(base+"/v1/objects", "application/json", strings.NewReader(body)) + if err != nil { + return "", 0, err.Error() + } + defer resp.Body.Close() + raw, _ := io.ReadAll(resp.Body) + var out struct { + ObjectID string `json:"object_id"` + Raw string `json:"raw"` + } + json.Unmarshal(raw, &out) // best effort; raw keeps the error text + if out.ObjectID != "" { + return out.ObjectID, resp.StatusCode, "" + } + return "", resp.StatusCode, string(raw) +} + +// post sends a raw JSON body and returns the response text. +func post(url, body string) string { + httpResp, err := http.Post(url, "application/json", strings.NewReader(body)) + if err != nil { + log.Fatal(err) + } + defer httpResp.Body.Close() + raw, _ := io.ReadAll(httpResp.Body) + return string(raw) +} + +// postJSON posts an empty/raw JSON body and parses the reply. +func postJSON(url, body string) map[string]any { + raw := post(url, body) + var m map[string]any + json.Unmarshal([]byte(raw), &m) + return m +} + +// getJSON fetches a small JSON object endpoint. +func getJSON(url string) map[string]any { + httpResp, err := http.Get(url) + if err != nil { + log.Fatal(err) + } + defer httpResp.Body.Close() + raw, _ := io.ReadAll(httpResp.Body) + var m map[string]any + json.Unmarshal(raw, &m) + return m +} + +// fetchResponse looks up the first response envelope for a request id. +func fetchResponse(base, token, idHex string) (tceB64, sigB64 string, ok bool) { + req, err := http.NewRequest("GET", base+"/v1/responses?request="+idHex, nil) + if err != nil { + return "", "", false + } + req.Header.Set("Authorization", "Bearer "+token) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", false + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return "", "", false + } + var doc struct { + Responses []struct { + Tce string `json:"tce"` + Signature string `json:"signature"` + } `json:"responses"` + } + json.NewDecoder(resp.Body).Decode(&doc) + if len(doc.Responses) == 0 { + return "", "", false + } + return doc.Responses[0].Tce, doc.Responses[0].Signature, true +} + +func nonce() []byte { + n := make([]byte, tce.NonceSize) + rand.Read(n) + return n +} diff --git a/internal/server/config.go b/internal/server/config.go index 48ee6bb..05dd1a9 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -4,7 +4,7 @@ import ( "os" "time" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" "gopkg.in/yaml.v3" ) @@ -28,6 +28,13 @@ type Config struct { SessionTTL time.Duration // validity of a verified auth session MaxBodyBytes int64 // hard cap on any request body + + // TrustProxy: when true, rate limiting keys clients by the + // X-Forwarded-For header sent by the reverse proxy in front of the + // relay instead of the socket address (which would collapse every + // visitor into one bucket). Only enable it when the relay is actually + // reachable exclusively through a proxy you control. + TrustProxy bool } // configFile mirrors Config but keeps durations as strings so they can be @@ -45,6 +52,7 @@ type configFile struct { ChallengeTTL string `yaml:"challenge_ttl"` SessionTTL string `yaml:"session_ttl"` MaxBodyBytes int64 `yaml:"max_body_bytes"` + TrustProxy bool `yaml:"trust_proxy"` } // DefaultConfig returns the built-in defaults. diff --git a/internal/server/invariants_test.go b/internal/server/invariants_test.go index 52e2a3d..7d99896 100644 --- a/internal/server/invariants_test.go +++ b/internal/server/invariants_test.go @@ -35,7 +35,7 @@ func TestServerDoesNotImportSigner(t *testing.T) { if err != nil { continue } - if p == "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" { + if p == "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" { t.Errorf("%s imports the signer package, violating INV-1", e.Name()) } } diff --git a/internal/server/ratelimit.go b/internal/server/ratelimit.go index d155a60..f0a4882 100644 --- a/internal/server/ratelimit.go +++ b/internal/server/ratelimit.go @@ -51,10 +51,41 @@ func (l *ipLimiter) allow(ip string) bool { } // clientIP returns the request's remote IP, stripping a port if present. -func clientIP(r *http.Request) string { +// With trustProxy set (Config.TrustProxy) the left-most X-Forwarded-For +// entry wins: the relay sits behind a reverse proxy and every socket would +// otherwise share the proxy's address. The header is attacker-controlled, +// which is exactly why trusting it is an explicit operator choice. +func clientIP(r *http.Request, trustProxy bool) string { + if trustProxy { + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + if i := indexByte(xff, ','); i >= 0 { + xff = xff[:i] + } + return trimSpace(xff) + } + } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr } return host } + +func indexByte(s string, b byte) int { + for i := 0; i < len(s); i++ { + if s[i] == b { + return i + } + } + return -1 +} + +func trimSpace(s string) string { + for len(s) > 0 && (s[0] == ' ' || s[0] == '\t') { + s = s[1:] + } + for len(s) > 0 && (s[len(s)-1] == ' ' || s[len(s)-1] == '\t') { + s = s[:len(s)-1] + } + return s +} diff --git a/internal/server/ratelimit_test.go b/internal/server/ratelimit_test.go index cb872f0..246b628 100644 --- a/internal/server/ratelimit_test.go +++ b/internal/server/ratelimit_test.go @@ -2,6 +2,7 @@ package server import ( "net/http" + "net/http/httptest" "testing" "time" ) @@ -48,13 +49,26 @@ func TestWindowReset(t *testing.T) { func TestClientIP(t *testing.T) { withPort, _ := http.NewRequest(http.MethodGet, "/", nil) withPort.RemoteAddr = "192.168.1.5:54321" - if got := clientIP(withPort); got != "192.168.1.5" { + if got := clientIP(withPort, false); got != "192.168.1.5" { t.Fatalf("expected 192.168.1.5, got %q", got) } noPort, _ := http.NewRequest(http.MethodGet, "/", nil) noPort.RemoteAddr = "10.0.0.1" - if got := clientIP(noPort); got != "10.0.0.1" { + if got := clientIP(noPort, false); got != "10.0.0.1" { t.Fatalf("expected 10.0.0.1, got %q", got) } } + +func TestClientIPTrustsForwardedFor(t *testing.T) { + r := httptest.NewRequest("POST", "/v1/objects", nil) + r.RemoteAddr = "10.0.0.9:55555" + r.Header.Set("X-Forwarded-For", "203.0.113.7, 10.0.0.1") + if got := clientIP(r, true); got != "203.0.113.7" { + t.Fatalf("trusted proxy: got %q, want 203.0.113.7", got) + } + // Without the knob the header must be ignored (spoofable). + if got := clientIP(r, false); got != "10.0.0.9" { + t.Fatalf("untrusted: got %q, want 10.0.0.9", got) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 1ce3094..2aa5bc0 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -12,10 +12,10 @@ import ( "sync" "time" - "git.n1ko.dev/Niko/niko_trust/internal/identity" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/identity" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" ) // Server is the trust relay: it stores signed objects and brokers @@ -30,6 +30,7 @@ type Server struct { maxBodyBytes int64 challengeTTL time.Duration sessionTTL time.Duration + trustProxy bool putLimiter *ipLimiter challengeLimiter *ipLimiter @@ -91,6 +92,7 @@ func New(cfg Config) *Server { maxBodyBytes: cfg.MaxBodyBytes, challengeTTL: cfg.ChallengeTTL, sessionTTL: cfg.SessionTTL, + trustProxy: cfg.TrustProxy, challenges: make(map[string]time.Time), sessions: make(map[string]session), ws: newWSHub(), @@ -125,7 +127,7 @@ func (s *Server) Handler() http.Handler { } func (s *Server) rateLimitPut(w http.ResponseWriter, r *http.Request) { - if !s.putLimiter.allow(clientIP(r)) { + if !s.putLimiter.allow(clientIP(r, s.trustProxy)) { writeErr(w, http.StatusTooManyRequests, "rate limited") return } @@ -133,7 +135,7 @@ func (s *Server) rateLimitPut(w http.ResponseWriter, r *http.Request) { } func (s *Server) rateLimitChallenge(w http.ResponseWriter, r *http.Request) { - if !s.challengeLimiter.allow(clientIP(r)) { + if !s.challengeLimiter.allow(clientIP(r, s.trustProxy)) { writeErr(w, http.StatusTooManyRequests, "rate limited") return } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index ddc7ef5..e8252ed 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -12,12 +12,12 @@ import ( "testing" "time" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" "git.n1ko.dev/Niko/niko_trust/internal/server" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/transport" - "git.n1ko.dev/Niko/niko_trust/internal/verify" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/verify" ) func newTestServer(t *testing.T) *httptest.Server { diff --git a/internal/server/store.go b/internal/server/store.go index 05d22c9..f4caac7 100644 --- a/internal/server/store.go +++ b/internal/server/store.go @@ -23,9 +23,9 @@ import ( "sync" "time" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" ) // Store is a content-addressed, in-memory store of signed objects. diff --git a/internal/server/ws.go b/internal/server/ws.go index e13a216..493b70a 100644 --- a/internal/server/ws.go +++ b/internal/server/ws.go @@ -20,8 +20,8 @@ import ( "github.com/coder/websocket" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" ) const ( diff --git a/internal/server/ws_test.go b/internal/server/ws_test.go index 7c29454..1bb010d 100644 --- a/internal/server/ws_test.go +++ b/internal/server/ws_test.go @@ -13,10 +13,10 @@ import ( "github.com/coder/websocket" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" ) func wsURL(httpURL string) string { diff --git a/internal/address/address.go b/pkg/address/address.go similarity index 100% rename from internal/address/address.go rename to pkg/address/address.go diff --git a/internal/address/address_test.go b/pkg/address/address_test.go similarity index 99% rename from internal/address/address_test.go rename to pkg/address/address_test.go index 14275ec..d995efb 100644 --- a/internal/address/address_test.go +++ b/pkg/address/address_test.go @@ -10,7 +10,7 @@ import ( "github.com/btcsuite/btcd/btcutil/bech32" - "git.n1ko.dev/Niko/niko_trust/internal/address" + "git.n1ko.dev/Niko/niko_trust/pkg/address" ) // deterministicKey returns a reproducible valid Ed25519 public key. diff --git a/internal/address/fuzz_test.go b/pkg/address/fuzz_test.go similarity index 99% rename from internal/address/fuzz_test.go rename to pkg/address/fuzz_test.go index 8c63f86..29a93ae 100644 --- a/internal/address/fuzz_test.go +++ b/pkg/address/fuzz_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/address" + "git.n1ko.dev/Niko/niko_trust/pkg/address" ) func seedCorpus(f *testing.F) { diff --git a/internal/address/helpers_test.go b/pkg/address/helpers_test.go similarity index 94% rename from internal/address/helpers_test.go rename to pkg/address/helpers_test.go index f88fd9a..fdc2a08 100644 --- a/internal/address/helpers_test.go +++ b/pkg/address/helpers_test.go @@ -5,7 +5,7 @@ import ( "github.com/btcsuite/btcd/btcutil/bech32" - "git.n1ko.dev/Niko/niko_trust/internal/address" + "git.n1ko.dev/Niko/niko_trust/pkg/address" ) // encodeRawForTest builds a syntactically valid bech32m trust address around diff --git a/internal/address/pubkey.go b/pkg/address/pubkey.go similarity index 100% rename from internal/address/pubkey.go rename to pkg/address/pubkey.go diff --git a/internal/address/pubkey_test.go b/pkg/address/pubkey_test.go similarity index 99% rename from internal/address/pubkey_test.go rename to pkg/address/pubkey_test.go index d375ddf..fa0fe79 100644 --- a/internal/address/pubkey_test.go +++ b/pkg/address/pubkey_test.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/address" + "git.n1ko.dev/Niko/niko_trust/pkg/address" ) // smallOrderKeys is the standard list of Edwards25519 points of order 1, 2, 4 diff --git a/internal/identity/alias.go b/pkg/identity/alias.go similarity index 100% rename from internal/identity/alias.go rename to pkg/identity/alias.go diff --git a/internal/identity/alias_test.go b/pkg/identity/alias_test.go similarity index 98% rename from internal/identity/alias_test.go rename to pkg/identity/alias_test.go index 07f29cb..15a38c2 100644 --- a/internal/identity/alias_test.go +++ b/pkg/identity/alias_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/identity" + "git.n1ko.dev/Niko/niko_trust/pkg/identity" ) func TestAliasAccepts(t *testing.T) { diff --git a/internal/identity/fuzz_test.go b/pkg/identity/fuzz_test.go similarity index 97% rename from internal/identity/fuzz_test.go rename to pkg/identity/fuzz_test.go index 6b14dd1..b4c765c 100644 --- a/internal/identity/fuzz_test.go +++ b/pkg/identity/fuzz_test.go @@ -4,8 +4,8 @@ import ( "crypto/ed25519" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/identity" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/identity" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" ) // FuzzVerifyNeverPanics asserts that verification is total over arbitrary diff --git a/internal/identity/identity.go b/pkg/identity/identity.go similarity index 99% rename from internal/identity/identity.go rename to pkg/identity/identity.go index fa34034..41dbaf5 100644 --- a/internal/identity/identity.go +++ b/pkg/identity/identity.go @@ -32,7 +32,7 @@ import ( "crypto/ed25519" "errors" - "git.n1ko.dev/Niko/niko_trust/internal/address" + "git.n1ko.dev/Niko/niko_trust/pkg/address" ) // ErrInvalidSignature is returned when a signature does not verify. diff --git a/internal/identity/identity_test.go b/pkg/identity/identity_test.go similarity index 98% rename from internal/identity/identity_test.go rename to pkg/identity/identity_test.go index 1986137..a87a656 100644 --- a/internal/identity/identity_test.go +++ b/pkg/identity/identity_test.go @@ -5,9 +5,9 @@ import ( "encoding/json" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/identity" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/identity" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" ) func mustSigner(t *testing.T) *signer.Signer { diff --git a/internal/identity/invariants_test.go b/pkg/identity/invariants_test.go similarity index 93% rename from internal/identity/invariants_test.go rename to pkg/identity/invariants_test.go index cc0dd20..f7da560 100644 --- a/internal/identity/invariants_test.go +++ b/pkg/identity/invariants_test.go @@ -84,8 +84,8 @@ func TestProtocolLayerImports(t *testing.T) { } protocolPackages := []string{ - modulePath + "/internal/address", - modulePath + "/internal/identity", + modulePath + "/pkg/address", + modulePath + "/pkg/identity", } for _, pkg := range protocolPackages { @@ -97,8 +97,8 @@ func TestProtocolLayerImports(t *testing.T) { continue case allowedExternal(dep): continue - case strings.HasPrefix(dep, modulePath+"/internal/address"), - strings.HasPrefix(dep, modulePath+"/internal/identity"): + case strings.HasPrefix(dep, modulePath+"/pkg/address"), + strings.HasPrefix(dep, modulePath+"/pkg/identity"): // Protocol packages may depend on each other. continue case strings.HasPrefix(dep, "internal/"), @@ -123,11 +123,11 @@ func TestProtocolLayerImports(t *testing.T) { // and transport layers cannot contain signing capability even by accident. func TestProtocolDoesNotImportSigner(t *testing.T) { requireToolchain(t) - signerPkg := modulePath + "/internal/identity/signer" + signerPkg := modulePath + "/pkg/identity/signer" for _, pkg := range []string{ - modulePath + "/internal/address", - modulePath + "/internal/identity", + modulePath + "/pkg/address", + modulePath + "/pkg/identity", } { for _, dep := range goList(t, "-deps", pkg) { if dep == signerPkg { @@ -154,7 +154,7 @@ func TestNoSigningOutsideSigner(t *testing.T) { } for _, file := range files { - if strings.Contains(file, "/internal/identity/signer/") { + if strings.Contains(file, "/pkg/identity/signer/") { continue } data, err := readFile(file) @@ -164,7 +164,7 @@ func TestNoSigningOutsideSigner(t *testing.T) { for _, api := range signingAPIs { if strings.Contains(data, api) { t.Errorf("%s uses %s outside the signer package; "+ - "private key operations must stay in internal/identity/signer", file, api) + "private key operations must stay in pkg/identity/signer", file, api) } } } diff --git a/internal/identity/signer/signer.go b/pkg/identity/signer/signer.go similarity index 98% rename from internal/identity/signer/signer.go rename to pkg/identity/signer/signer.go index f0fe67b..7956850 100644 --- a/internal/identity/signer/signer.go +++ b/pkg/identity/signer/signer.go @@ -22,8 +22,8 @@ import ( "errors" "io" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/identity" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/identity" ) var ( diff --git a/internal/identity/testhelpers_test.go b/pkg/identity/testhelpers_test.go similarity index 100% rename from internal/identity/testhelpers_test.go rename to pkg/identity/testhelpers_test.go diff --git a/internal/protocol/accessors_test.go b/pkg/protocol/accessors_test.go similarity index 98% rename from internal/protocol/accessors_test.go rename to pkg/protocol/accessors_test.go index e5f8f7e..aa8b9d3 100644 --- a/internal/protocol/accessors_test.go +++ b/pkg/protocol/accessors_test.go @@ -3,8 +3,8 @@ package protocol_test import ( "testing" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // TestAccessors covers the trivial Value/object accessors that the golden and diff --git a/internal/protocol/decode.go b/pkg/protocol/decode.go similarity index 99% rename from internal/protocol/decode.go rename to pkg/protocol/decode.go index c82a6e9..f060492 100644 --- a/internal/protocol/decode.go +++ b/pkg/protocol/decode.go @@ -3,8 +3,8 @@ package protocol import ( "bytes" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Decoders for the six protocol objects. diff --git a/internal/protocol/encode.go b/pkg/protocol/encode.go similarity index 98% rename from internal/protocol/encode.go rename to pkg/protocol/encode.go index 06681c6..5c4c700 100644 --- a/internal/protocol/encode.go +++ b/pkg/protocol/encode.go @@ -3,8 +3,8 @@ package protocol import ( "fmt" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Encoders for the six protocol objects. diff --git a/internal/protocol/example_test.go b/pkg/protocol/example_test.go similarity index 94% rename from internal/protocol/example_test.go rename to pkg/protocol/example_test.go index 0f100f4..dbfc2d8 100644 --- a/internal/protocol/example_test.go +++ b/pkg/protocol/example_test.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // ExampleClaimLifecycle is the whole client-side story for one claim, with no diff --git a/internal/protocol/fuzz.go b/pkg/protocol/fuzz.go similarity index 100% rename from internal/protocol/fuzz.go rename to pkg/protocol/fuzz.go diff --git a/internal/protocol/fuzz_test.go b/pkg/protocol/fuzz_test.go similarity index 97% rename from internal/protocol/fuzz_test.go rename to pkg/protocol/fuzz_test.go index ee28882..7d7617f 100644 --- a/internal/protocol/fuzz_test.go +++ b/pkg/protocol/fuzz_test.go @@ -8,9 +8,9 @@ import ( "reflect" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // loadVectorsF is loadVectors for the seed-corpus phase of a fuzz test, diff --git a/internal/protocol/helpers_test.go b/pkg/protocol/helpers_test.go similarity index 98% rename from internal/protocol/helpers_test.go rename to pkg/protocol/helpers_test.go index 1b76e76..c3a4eae 100644 --- a/internal/protocol/helpers_test.go +++ b/pkg/protocol/helpers_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" ) const vectorsPath = "../../testdata/vectors/tce_vectors.json" diff --git a/internal/protocol/invariants_test.go b/pkg/protocol/invariants_test.go similarity index 95% rename from internal/protocol/invariants_test.go rename to pkg/protocol/invariants_test.go index 770c738..db0c132 100644 --- a/internal/protocol/invariants_test.go +++ b/pkg/protocol/invariants_test.go @@ -81,8 +81,8 @@ func TestAllowedImports(t *testing.T) { "crypto/subtle": true, "errors": true, "fmt": true, - "git.n1ko.dev/Niko/niko_trust/internal/address": true, - "git.n1ko.dev/Niko/niko_trust/internal/tce": true, + "git.n1ko.dev/Niko/niko_trust/pkg/address": true, + "git.n1ko.dev/Niko/niko_trust/pkg/tce": true, } for _, f := range scanPackageProtocol(t, ".") { for _, imp := range importsOfProtocol(t, f) { diff --git a/internal/protocol/mutation_test.go b/pkg/protocol/mutation_test.go similarity index 98% rename from internal/protocol/mutation_test.go rename to pkg/protocol/mutation_test.go index 6356d1d..a61e01d 100644 --- a/internal/protocol/mutation_test.go +++ b/pkg/protocol/mutation_test.go @@ -3,7 +3,7 @@ package protocol_test import ( "testing" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" ) // Mutation tests: any single-byte change to the canonical bytes or to the diff --git a/internal/protocol/objects.go b/pkg/protocol/objects.go similarity index 99% rename from internal/protocol/objects.go rename to pkg/protocol/objects.go index 382c558..a173d09 100644 --- a/internal/protocol/objects.go +++ b/pkg/protocol/objects.go @@ -19,7 +19,7 @@ import ( "bytes" "errors" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Errors returned when an object violates a protocol rule. diff --git a/internal/protocol/rejects_test.go b/pkg/protocol/rejects_test.go similarity index 97% rename from internal/protocol/rejects_test.go rename to pkg/protocol/rejects_test.go index 7cafd59..41b80d0 100644 --- a/internal/protocol/rejects_test.go +++ b/pkg/protocol/rejects_test.go @@ -4,8 +4,8 @@ import ( "errors" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // TestRejectVectors runs the frozen malformed-encoding vectors from diff --git a/internal/protocol/rotation.go b/pkg/protocol/rotation.go similarity index 98% rename from internal/protocol/rotation.go rename to pkg/protocol/rotation.go index 5429ad5..6debc62 100644 --- a/internal/protocol/rotation.go +++ b/pkg/protocol/rotation.go @@ -22,8 +22,8 @@ import ( "bytes" "crypto/subtle" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // KeyRotationRequest is the incoming key's claim of succession, tag 0x08. diff --git a/internal/protocol/rules_test.go b/pkg/protocol/rules_test.go similarity index 99% rename from internal/protocol/rules_test.go rename to pkg/protocol/rules_test.go index 0a937f7..0578904 100644 --- a/internal/protocol/rules_test.go +++ b/pkg/protocol/rules_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Rule tests for the per-object constraints of PROTOCOL.md section 8. The diff --git a/internal/protocol/vectors_test.go b/pkg/protocol/vectors_test.go similarity index 98% rename from internal/protocol/vectors_test.go rename to pkg/protocol/vectors_test.go index 3106467..9343ebf 100644 --- a/internal/protocol/vectors_test.go +++ b/pkg/protocol/vectors_test.go @@ -5,9 +5,9 @@ import ( "encoding/json" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Golden tests against the frozen reference vectors. diff --git a/internal/protocol/verify.go b/pkg/protocol/verify.go similarity index 99% rename from internal/protocol/verify.go rename to pkg/protocol/verify.go index 895bccf..6245935 100644 --- a/internal/protocol/verify.go +++ b/pkg/protocol/verify.go @@ -4,7 +4,7 @@ import ( "crypto/ed25519" "crypto/subtle" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Verification of the six protocol objects, following the order of diff --git a/internal/protocol/verify_test.go b/pkg/protocol/verify_test.go similarity index 98% rename from internal/protocol/verify_test.go rename to pkg/protocol/verify_test.go index 140733c..94b7a0b 100644 --- a/internal/protocol/verify_test.go +++ b/pkg/protocol/verify_test.go @@ -4,9 +4,9 @@ import ( "errors" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Verification tests for PROTOCOL.md sections 7.3 and 8. The signature order diff --git a/internal/tce/decoder.go b/pkg/tce/decoder.go similarity index 100% rename from internal/tce/decoder.go rename to pkg/tce/decoder.go diff --git a/internal/tce/encoder.go b/pkg/tce/encoder.go similarity index 100% rename from internal/tce/encoder.go rename to pkg/tce/encoder.go diff --git a/internal/tce/fuzz.go b/pkg/tce/fuzz.go similarity index 100% rename from internal/tce/fuzz.go rename to pkg/tce/fuzz.go diff --git a/internal/tce/fuzz_test.go b/pkg/tce/fuzz_test.go similarity index 100% rename from internal/tce/fuzz_test.go rename to pkg/tce/fuzz_test.go diff --git a/internal/tce/id.go b/pkg/tce/id.go similarity index 100% rename from internal/tce/id.go rename to pkg/tce/id.go diff --git a/internal/tce/id_test.go b/pkg/tce/id_test.go similarity index 98% rename from internal/tce/id_test.go rename to pkg/tce/id_test.go index cc1d1f3..0977b62 100644 --- a/internal/tce/id_test.go +++ b/pkg/tce/id_test.go @@ -3,7 +3,7 @@ package tce_test import ( "testing" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) func TestIDAccessors(t *testing.T) { diff --git a/internal/tce/invariants_test.go b/pkg/tce/invariants_test.go similarity index 100% rename from internal/tce/invariants_test.go rename to pkg/tce/invariants_test.go diff --git a/internal/tce/number.go b/pkg/tce/number.go similarity index 100% rename from internal/tce/number.go rename to pkg/tce/number.go diff --git a/internal/tce/number_test.go b/pkg/tce/number_test.go similarity index 98% rename from internal/tce/number_test.go rename to pkg/tce/number_test.go index f75af97..600c478 100644 --- a/internal/tce/number_test.go +++ b/pkg/tce/number_test.go @@ -3,7 +3,7 @@ package tce_test import ( "testing" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // TestCanonicalNumber exercises section 5.1: arbitrary-precision decimals must diff --git a/internal/tce/primitives_test.go b/pkg/tce/primitives_test.go similarity index 100% rename from internal/tce/primitives_test.go rename to pkg/tce/primitives_test.go diff --git a/internal/tce/tce.go b/pkg/tce/tce.go similarity index 100% rename from internal/tce/tce.go rename to pkg/tce/tce.go diff --git a/internal/tce/testdata/fuzz/FuzzStringValidation/f921751fe02821d6 b/pkg/tce/testdata/fuzz/FuzzStringValidation/f921751fe02821d6 similarity index 100% rename from internal/tce/testdata/fuzz/FuzzStringValidation/f921751fe02821d6 rename to pkg/tce/testdata/fuzz/FuzzStringValidation/f921751fe02821d6 diff --git a/internal/tce/value.go b/pkg/tce/value.go similarity index 100% rename from internal/tce/value.go rename to pkg/tce/value.go diff --git a/internal/tce/value_test.go b/pkg/tce/value_test.go similarity index 98% rename from internal/tce/value_test.go rename to pkg/tce/value_test.go index eeb4e33..ff10038 100644 --- a/internal/tce/value_test.go +++ b/pkg/tce/value_test.go @@ -3,7 +3,7 @@ package tce_test import ( "testing" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) func TestValueConstructorsAndAccessors(t *testing.T) { diff --git a/internal/tce/vectors_test.go b/pkg/tce/vectors_test.go similarity index 100% rename from internal/tce/vectors_test.go rename to pkg/tce/vectors_test.go diff --git a/internal/transport/envelope.go b/pkg/transport/envelope.go similarity index 97% rename from internal/transport/envelope.go rename to pkg/transport/envelope.go index 3ae020d..bf9b2b4 100644 --- a/internal/transport/envelope.go +++ b/pkg/transport/envelope.go @@ -13,8 +13,8 @@ import ( "encoding/json" "fmt" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) // Envelope is the JSON transport form of one signed object. diff --git a/internal/transport/envelope_test.go b/pkg/transport/envelope_test.go similarity index 86% rename from internal/transport/envelope_test.go rename to pkg/transport/envelope_test.go index d45b517..c42cd46 100644 --- a/internal/transport/envelope_test.go +++ b/pkg/transport/envelope_test.go @@ -3,10 +3,10 @@ package transport_test import ( "testing" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" ) func TestEnvelopeViewAndVerify(t *testing.T) { diff --git a/internal/transport/view.go b/pkg/transport/view.go similarity index 97% rename from internal/transport/view.go rename to pkg/transport/view.go index 31ba847..146a03f 100644 --- a/internal/transport/view.go +++ b/pkg/transport/view.go @@ -4,9 +4,9 @@ import ( "encoding/hex" "encoding/json" - "git.n1ko.dev/Niko/niko_trust/internal/identity" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/identity" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" ) func hexStr(b []byte) string { return hex.EncodeToString(b) } diff --git a/internal/verify/delegation_test.go b/pkg/verify/delegation_test.go similarity index 96% rename from internal/verify/delegation_test.go rename to pkg/verify/delegation_test.go index eca7ae4..b9c2df1 100644 --- a/internal/verify/delegation_test.go +++ b/pkg/verify/delegation_test.go @@ -4,12 +4,12 @@ import ( "bytes" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/address" + "git.n1ko.dev/Niko/niko_trust/pkg/address" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/verify" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/verify" ) // delegationFixture builds a graph plus signers for chain scenarios. diff --git a/internal/verify/rotation_test.go b/pkg/verify/rotation_test.go similarity index 95% rename from internal/verify/rotation_test.go rename to pkg/verify/rotation_test.go index 10d76a2..f20e66e 100644 --- a/internal/verify/rotation_test.go +++ b/pkg/verify/rotation_test.go @@ -4,11 +4,11 @@ import ( "bytes" "testing" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/verify" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/verify" ) // rotationFixture stores both halves of a rotation link. diff --git a/internal/verify/verify.go b/pkg/verify/verify.go similarity index 98% rename from internal/verify/verify.go rename to pkg/verify/verify.go index ba3dd2f..8d52712 100644 --- a/internal/verify/verify.go +++ b/pkg/verify/verify.go @@ -14,11 +14,11 @@ import ( "sort" "sync" - "git.n1ko.dev/Niko/niko_trust/internal/address" - "git.n1ko.dev/Niko/niko_trust/internal/identity" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/address" + "git.n1ko.dev/Niko/niko_trust/pkg/identity" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" ) // Graph holds the verified objects under evaluation. diff --git a/internal/verify/verify_test.go b/pkg/verify/verify_test.go similarity index 97% rename from internal/verify/verify_test.go rename to pkg/verify/verify_test.go index 96c3418..fb74e67 100644 --- a/internal/verify/verify_test.go +++ b/pkg/verify/verify_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - "git.n1ko.dev/Niko/niko_trust/internal/identity/signer" - "git.n1ko.dev/Niko/niko_trust/internal/protocol" - "git.n1ko.dev/Niko/niko_trust/internal/tce" - "git.n1ko.dev/Niko/niko_trust/internal/transport" - "git.n1ko.dev/Niko/niko_trust/internal/verify" + "git.n1ko.dev/Niko/niko_trust/pkg/identity/signer" + "git.n1ko.dev/Niko/niko_trust/pkg/protocol" + "git.n1ko.dev/Niko/niko_trust/pkg/tce" + "git.n1ko.dev/Niko/niko_trust/pkg/transport" + "git.n1ko.dev/Niko/niko_trust/pkg/verify" - "git.n1ko.dev/Niko/niko_trust/internal/address" + "git.n1ko.dev/Niko/niko_trust/pkg/address" ) const base = uint64(1_700_000_000) diff --git a/service b/service new file mode 100755 index 0000000..b190d1b Binary files /dev/null and b/service differ