9.88K subscribers
17K photos
366 videos
1 file
8.37K links
방장: @BQrypto @bqdeveloper @abrwda
*모든 의견은 개인의 의견입니다. DYOR

✏️공지방: https://t.me/BQTelegram
💁‍♂️소통방: https://t.me/BQKoreachat
Download Telegram
📌 내가 ZK를 공부하는 이유 #1: trade-off의 구조적 변화

BQ개발자입니다.

요즘 ZK(영지식증명)에 깊이 빠져있습니다.
처음엔 단순한 암호화 기법 정도로 생각했는데,
실제로 이해하고 나니 문제를 바라보는 관점 자체가 바뀌더라구요.

앞으로 몇 편에 걸쳐 제가 왜 ZK를 공부하게 됐는지,
그리고 왜 이게 단순한 기술 학습을 넘어서는 의미를 갖는지 정리해보려 합니다.

첫 번째는 ZK가 어떻게 기존의 기술적 타협점들을 해소하는지에 대한 이야기입니다.


🔍 모든 시스템에 존재하는 trade-off

소프트웨어 개발에서 가장 자주 듣는 말 중 하나가 "trade-off"입니다.

- 보안을 강화하면 → 사용성이 떨어진다
- 속도를 높이면 → 비용이 증가한다
- 탈중앙화하면 → 효율성이 감소한다

특히 프라이버시와 검증가능성은 가장 극명한 trade-off로 여겨졌습니다.
뭔가를 증명하려면 정보를 공개해야 하고, 정보를 숨기면 검증할 수 없다는 게 상식이었죠.


💭 기존 검증 방식의 구조

검증이 작동하는 방식은 단순했습니다:

1. A가 주장을 한다
2. B가 증거를 요구한다
3. A가 증거를 보여준다
4. B가 확인한다

이 과정에서 B는 필연적으로 정보를 알게 됩니다.

그래서 실무에서는 늘 타협해야 했습니다:
- 감사를 받으려면 장부를 공개
- 자격을 증명하려면 신분증 제시
- 규정 준수를 확인받으려면 내부 데이터 공유


🔓 ZK가 바꾼 것

ZK는 검증의 구조를 바꿉니다.

기존: 정보를 보여줌 → 검증자가 직접 확인
ZK: 정보는 숨김 → 특정 조건을 만족한다는 사실만 증명

예를 들어, 대출 심사에서:
- 기존: 통장 잔고 1,234,567원 공개
- ZK: "잔고가 100만원 이상임"만 증명

작은 차이 같지만, 이는 검증의 본질을 바꿉니다.
검증자는 필요한 사실만 확인하고, 그 이상은 알 수 없습니다.


✏️ 실제로 무엇이 달라지는가

이런 변화가 만드는 구체적인 차이:

감사 시나리오 :
- Before: 전체 거래 내역 제출 → 민감한 거래처 정보 노출
- After: "모든 거래가 규정을 준수했음"만 증명 → 거래처 정보 보호

블록체인에서 :
- Before: 모든 거래 투명하게 공개 → 프라이버시 포기
- After: 거래 유효성만 증명 → 프라이버시 유지

AI 모델 검증 :
- Before: 모델 공개 → 지적재산권 포기
- After: "편향성이 기준치 이하임"만 증명 → 모델 보호

이전에는 불가능했던 조합들이 이제는 가능해지는거죠

검증받으면서도 비밀을 유지할 수 있다는 것.


이 개념은 저한테 더 큰 의미로 다가옵니다.

"둘 중 하나를 선택해야 한다"는 제약에서 벗어나, "둘 다 가능하다"는 새로운 제안이 나온거니까요.
이는 0과 1의 선택이 아닌, 그 사이 어딘가에 새로운 균형점을 만들어낸다는 의미로 저는 해석됩니다.

기술의 발전은 대부분 이런 식이었다고 생각해요. 한 번에 모든 문제를 해결하는 마법 같은 해법은 잘 없으니까요.
하지만 ZK가 보여주는 것은 "타협의 정도를 바꿀 수 있다"는 가능성입니다.
그리고 이 가능성을 이해하는 사람만이 다음 단계의 개선을 만들어낼 수 있다고 생각하구요

제가 ZK를 공부하는 이유도 여기에 있습니다.

다음 편에서는 ZK가 만드는 또 다른 변화에 대해 이야기하겠습니다.

---

여러분의 좋아요와 공유는 사랑입니다 🥰

#BQ개발자 #ZK #영지식증명 #불가능을가능으로
Please open Telegram to view this post
VIEW IN TELEGRAM
❤20👍4
📌 내가 ZK를 공부하는 이유 #2: 같은 칼, 다른 용도

안녕하세요, BQ개발자입니다.

지난 편에서 ZK가 어떻게 불가능했던 것들을 가능하게 만드는지 이야기했습니다.

📱내가 ZK를 공부하는 이유 #1: trade-off의 구조적 변화

이번엔 제가 ZK를 완전히 다르게 보게 된 계기에 대해서 말해보겠습니다.

🎭 두 개의 ZK 프로젝트

두 개의 ZK 프로젝트를 한번 볼까요?

프로젝트 A: Tornado Cash
- 이더리움 거래를 완전히 익명화
- 누가 누구에게 보냈는지 추적 불가능
- 금융 프라이버시의 극대화

프로젝트 B: Worldcoin
- 홍채 스캔으로 전 세계 유일한 ID 생성
- 한 사람은 하나의 ID만 가능
- 중복 계정 원천 차단

똑같은 ZK 기술을 쓰는데, 하나는 제 정보를 숨기고, 하나는 제 정보를 고정시킵니다.


💡 갑자기 보인 것

이 두 프로젝트를 나란히 놓고 보니 뭔가 이상했습니다.

Tornado Cash에서 ZK의 역할:
- "이 돈이 누구 것인지 증명하지 않겠다"
- 연결고리를 끊는 도구

Worldcoin에서 ZK의 역할:
- "이 사람이 유일하다는 걸 증명하겠다"
- 연결고리를 만드는 도구


같은 기술이 정확히 반대로 사용되고 있었습니다.


🔍 진짜 차이는 뭘까

코드를 뜯어봐도 둘 다 ZK를 제대로 구현했습니다.

그럼 뭐가 이렇게 다른 결과를 만들었을까요?

설계 철학의 차이였습니다.

Tornado Cash:
- 목표: 금융 거래의 프라이버시
- 설계: 입금과 출금의 연결 끊기
- 결과: 추적 불가능

Worldcoin:
- 목표: 1인 1계정 보장
- 설계: 생체정보와 ID 영구 결합
- 결과: 완벽한 식별


✏️ 이게 왜 중요한가

비탈릭 부테린이 Worldcoin을 비판하며 했던 말이 이제야 이해됐습니다.

"만약 모든 서비스가 Worldcoin ID를 요구하기 시작한다면?"

생각해보세요:
- 익명 게시판? "Worldcoin ID로 로그인하세요"
- 새로운 지갑 생성? "Worldcoin ID 연동 필수"
- VPN 사용? "Worldcoin ID 인증 후 가능"

ZK로 만든 "수학적으로 완벽한 신원 증명"이
오히려 "수학적으로 완벽한 감시 시스템"이 될 수 있는 거죠.

반면 Tornado Cash는 정반대입니다.
ZK로 만든 "수학적으로 완벽한 연결 끊기"가
진짜 프라이버시를 가능하게 합니다.


🤷 그래서 내가 깨달은 것

ZK는 도구입니다.
망치가 집을 지을 수도, 부술 수도 있듯이
ZK도 자유를 늘릴 수도, 줄일 수도 있습니다.

차이는 오직 하나.
"무엇을 위해 설계했는가"

이제 저는 ZK 프로젝트를 볼 때 이렇게 보고 있습니다:
- 이게 연결을 끊는가, 만드는가?
- 선택권을 늘리는가, 줄이는가?
- 통제를 분산시키는가, 집중시키는가?


기술 스펙보다 이런 질문들이 더 중요하더라구요.

ZK를 공부하는 이유중 하나는
적어도 어떤 시스템이 나를 자유롭게 하는지,
아니면 더 촘촘히 묶는지는 구분하고 싶어서도 있는거죠

다음 편에서는 ZK를 공부하면서 제 사고방식이 어떻게 바뀌었는지 이야기하겠습니다.

---

여러분의 좋아요와 공유는 사랑입니다 🥰

#BQ개발자 #ZK #영지식증명 #설계의중요성
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍4
📌 내가 ZK를 공부하는 이유 #3: 순서가 아닌 조건으로 생각하기

안녕하세요, BQ개발자입니다.

📱내가 ZK를 공부하는 이유 #2: 같은 칼, 다른 용도

지난 편들에서 ZK가 불가능을 가능하게 만들고, 설계에 따라 전혀 다른 결과를 낸다는 걸 이야기했습니다.

이번엔 ZK 프로그래밍의 독특한 사고방식에 대해서입니다.


🤯 ZK는 거꾸로 생각한다

ZK 코드의 구조는 일반 프로그래밍과 다릅니다.

일반 프로그래밍: "이렇게 해서 저렇게 하면 결과가 나온다"
ZK 프로그래밍: "이 조건들이 참이어야 한다"

송금을 예로 들면:
- 일반: A에서 100원 빼기 → B에 100원 더하기 → 완료
- ZK: 전체 금액 합 = 변경전 합 AND 모든_잔액 ≥ 0

절차가 아닌 조건으로 표현됩니다.


💡 숨겨야 하니까 조건만 남는다

이런 차이가 생기는 이유는 ZK의 작동 원리 때문입니다.

ZK는 "과정은 비밀, 결과만 공개"라는 특성을 가집니다.
검증자는 어떤 순서로 계산했는지 알 수 없습니다.
따라서 최종 상태가 올바른 조건을 만족하는지만 확인합니다.

송금 예시의 경우:
- 검증자는 "누가 누구에게 얼마를 보냈는지" 과정을 모름
- "전체 돈의 합이 그대로인지"만 확인
- "음수 잔액이 없는지"만 체크

과정을 검증할 수 없으므로, 결과가 만족해야 할 조건으로 정의합니다.


🎯 이게 왜 더 나을까?

조건 기반 접근의 실질적 장점이 있습니다.

순서대로 하면 생기는 문제:
1. A에서 100원 차감 ✓
2. [여기서 시스템 다운!]
3. B에 100원 추가 ✗
→ 결과: 100원이 사라짐

조건으로 정의하면:
조건: 전체_금액_합 = 일정
→ 이 조건이 깨지면 거래 자체가 무효

중간 실패 시에도 일관성이 유지됩니다.

또한 순서가 없으므로 병렬 처리가 가능합니다.
여러 거래를 동시에 처리해도 최종 조건만 만족하면 됩니다.


✏️ 프로그래밍을 넘어선 사고법

이런 접근은 시스템 설계 관점을 바꿉니다.

"어떻게 구현할 것인가" 대신
"무엇이 항상 참이어야 하는가"를 먼저 정의합니다.

각 시스템의 불변식(invariant):
- 은행: 전체 돈의 합은 일정하다
- 투표: 투표수 = 투표자수
- 재고관리: 입고 - 출고 = 현재재고

이 불변식들이 시스템의 핵심 속성을 정의하는거죠.


💭 ZK가 가르쳐준 것

ZK 프로그래밍은 "무엇이 불변인가"를 먼저 정의해야 합니다.
이는 단순한 구현 기법이 아닌 설계 철학의 차이입니다.

복잡한 절차를 단순한 조건들로 표현하는 방법.
순서가 아닌 본질적 속성으로 시스템을 정의하는 방법.

이런 사고의 전환이 ZK를 공부하는 또 다른 이유입니다.

다음 마지막 편에서는 왜 지금이 ZK를 시작하기 좋은 시점인지 이야기하겠습니다.

---

여러분의 좋아요와 공유는 사랑입니다 🥰

#BQ개발자 #ZK #영지식증명 #조건적사고
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥1
📌 내가 ZK를 공부하는 이유 #4: 보이지 않는 시스템이 늘어나고 있다

안녕하세요, BQ개발자입니다.

📱내가 ZK를 공부하는 이유 #3: 순서가 아닌 조건으로 생각하기

지난 편들에서 ZK가 만드는 변화들을 살펴봤습니다.
마지막으로, 왜 하필 지금 ZK를 공부해야 하는지에 대한 생각을 정리해봤습니다.


🔍 이미 시작된 변화

최근 나온 서비스들을 보면 흥미로운 패턴이 보입니다.

Polygon ID: "18세 이상임"만 증명, 생년월일 비공개
Sismo: 특정 NFT 보유 증명하되 지갑 주소 비공개
Semaphore: 그룹 멤버임을 증명하되 누구인지는 비공개


공통점은 ZK를 사용한다는 것.
더 중요한 건, 사용자 대부분이 ZK인지 모른다는 점인 것 같습니다.


💡 판단할 수 있는 사람의 부재

앞서 본 것처럼 ZK는 설계에 따라 결과가 달라집니다.

Tornado Cash처럼 프라이버시를 강화할 수도,
Worldcoin처럼 완벽한 추적 시스템이 될 수도 있죠.

문제는 이를 구분할 수 있는 사람이 거의 없다는 것 같아요.

일반 개발자도 ZK를 이해하기 어려워합니다.
암호학 지식이 필요하고, 사고방식도 다르니까요.
하물며 일반 사용자는 더더욱 판단이 어렵겠죠.


🎯 격차가 만드는 기회

이 격차가 역설적으로 기회를 만드는 것 같습니다.

현재 상황을 보면:
- ZK 시스템을 만드는 사람: 소수
- ZK 시스템을 이해하는 사람: 극소수
- ZK 시스템을 사용하는 사람: 급증할 것으로 보임
수요와 공급의 불균형이 생기는 것 같아요.

특히 2편에서 본 것처럼,
"이 시스템이 나를 자유롭게 하는가, 통제하는가"를
판단할 수 있는 능력은 더욱 희소할 것 같습니다.


✏️ 아직 초기지만 가능성이 보인다

ZK는 여전히 어렵습니다.

증명 생성 시간
- 복잡한 연산: 여전히 분 단위
- 간단한 증명: 수초~수십초
- 하지만 계속 개선 중

구현 난이도
- 라이브러리가 나왔지만 여전히 복잡
- 제대로 하려면 암호학 이해 필요
- 그래도 예전보다는 접근 가능해짐

아직 완벽하지 않습니다.
하지만 실용화될 수 있는 수준에는 도달하고 있는 것 같아요.
그래서 지금 공부를 시작하는 거고요.


🤔 지금이 적기인 이유

기술 학습에는 타이밍이 있는 것 같습니다.

너무 이르면: 실용성이 없어 동기부여가 어려움
너무 늦으면: 이미 경쟁이 심해 차별화가 어려움

ZK는 지금이 딱 그 중간인 것 같아요.

- 기술은 충분히 성숙한 듯
- 하지만 아직 주류는 아님
- 실제 프로젝트들이 나오기 시작
- 그러나 전문가는 여전히 부족

1편에서 본 "불가능을 가능하게" 하는 도구가
3편의 "조건적 사고"를 통해 구현되고
2편처럼 "설계에 따라" 다른 미래를 만들 시점.

그 시점이 바로 지금인 것 같습니다.


💭 내가 선택한 이유

ZK를 공부하기로 한 이유를 정리하면:

1. 문제를 새롭게 보는 도구가 될 것 같아서
2. 아직 소수만 이해하는 기술이라서
3. 곧 많은 곳에 적용될 것 같아서
4. 무엇보다, 시스템을 판단하는 눈이 필요할 것 같아서

기술 자체보다 중요한 건
그것이 만드는 변화를 이해하고
올바른 방향을 판단할 수 있는 능력인 것 같거든요.

ZK가 그런 능력을 기르는 좋은 출발점이 될 것 같습니다.


🔄 마치며

이렇게 4편에 걸쳐 ZK를 공부하는 이유를 정리했습니다.

결국 제가 생각하는 핵심은 하나입니다.
기술이 만드는 변화를 이해하고
그 방향을 판단할 수 있는 사람이 되고 싶다는 것.

아직 어렵고 낯설지만,
그래서 더 가치가 있을 것 같다는 생각이 듭니다.

---

여러분의 좋아요와 공유는 사랑입니다 🥰

#BQ개발자 #ZK #영지식증명 #지금시작하기
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍10❤6
📌 ZK 개발자가 알아야 할 핵심 개념 1편: 유한체 - 오버플로우가 사라지는 세계

안녕하세요, BQ개발자입니다.

ZK 개발을 시작하려는 분들을 위한 시리즈를 시작합니다.

첫 주제는 유한체입니다.

Circom이든 Noir이든, 모든 ZK 언어는 유한체 위에서만 동작합니다.
일반 정수가 아니라요. 이걸 모르면 내 코드가 왜 이상하게 동작하는지 이해할 수 없습니다.


❓유한체란?

컴퓨터는 숫자를 저장할 때 정해진 칸을 사용합니다. uint256은 256개의 0과 1로 숫자를 표현하는데,
가장 큰 수는 115792089237316195423570985008687907853269984665640564039457584007913129639935입니다. 78자리 숫자죠.

이 숫자에 1을 더하면 어떻게 될까요? 257번째 자리가 필요한데, 그런 공간이 없습니다.
그래서 모든 자리가 0으로 리셋됩니다. 실제로 100억 원이 있던 토큰 잔액이 1원을 더 받는 순간 0원이 되어버린 해킹 사고가 있었습니다.

유한체는 이 "최댓값 다음이 0"이라는 현상을 아예 정상 동작으로 만들어버린 시스템입니다. 버그가 아니라 원래 그렇게 설계한 거죠.



🤔 uint256의 끝에서 일어나는 일

솔리디티 개발자라면 늘 조심하는 상황이 있습니다.

uint256 최댓값에 1을 더하면 0이 되기 때문이죠.
다시 말해 256비트로 표현할 수 있는 한계를 넘어서면, 다시 0으로 돌아가는 상황을 겪게되고,

이게 정수 오버플로우입니다.

TimeLock 해킹이 바로 이걸 악용했던 사례죠
미래 시점의 잠금에 큰 수를 더해서 0으로 만들어버린 다음
즉시 출금이 가능하도록 해서 해킹이 가능했습니다..ㄷㄷ


1️⃣ 오버플로우를 막으려는 노력

이 문제를 해결하려고 개발자들은 계속 방어 코드를 추가해왔습니다.

SafeMath 라이브러리를 쓰거나, Solidity 0.8.0부터는 컴파일러가 자동으로 체크하죠
하지만 이건 비용입니다. 모든 연산마다 "오버플로우 체크"가 추가로 실행되니까요.

문제의 핵심은 이거죠.

일반 정수 시스템에서 오버플로우는 예외적 오류라는점


그래서 막아야 하고, 막는 데 비용이 듭니다.


2️⃣ 유한체의 완전히 다른 관점

ZK에서 사용하는 유한체 Fp를 보겠습니다.

0부터 p-1까지의 정수로 이루어져 있고, 모든 연산이 mod p로 정의됩니다.
p-1에 1을 더하면? 0이 됩니다.

이건 오류가 아니라 정의죠.

유한체는 이렇게 처음부터 "순환하는 시스템"으로 설계되었습니다.
p를 넘으면 다시 0부터 시작하는 게 정상 동작이죠.


3️⃣ 왜 이게 더 나은가

차이를 정확히 짚어보겠습니다.

일반 정수에서 오버플로우는 보통 예외상황으로 봅니다.
대부분의 연산은 정상 범위에서 일어나고, 가끔 경계를 넘을 때만 문제가 생기죠.
그래서 매번 체크해야 합니다.

하지만 유한체에서는 순환이 정상입니다.
애초에 모든 연산이 mod p로 정의되어 있으니, 체크할 필요가 없습니다.
오버플로우라는 개념 자체가 없는 거죠.

Circom에서 그냥 `sum <== a + b`라고 쓸 수 있는 이유입니다.
결과가 p를 넘든 안 넘든, 항상 유효한 값이 나오니까요.


✏️ 버그가 사라진 세계

유한체를 처음 접했을 때 깨달은 점이 있습니다.

우리가 "오버플로우 버그"라고 부르던 것은, 사실 무한을 유한한 공간에 억지로 담으려다 생긴 문제였다는점!
당연하게도 메모리는 유한한데 거기에 무한하게 큰 수를 담을 수 없으니까요.

유한체는 처음부터 유한하다고 가정합니다.
그래서 모든 숫자가 0부터 p-1 사이에 있고, 모든 연산 결과도 그 안에 있도록 하는거죠
무한을 다루려는 시도 자체가 없으니, 오버플로우도 없습니다.

ZK 회로를 작성할 때 SafeMath 같은 게 필요 없는 이유가 여기 있습니다.
오버플로우 걱정 자체를 할 필요가 없으니까요.

다음 편에서는 "음수가 없는데 뺄셈은 어떻게?"를 다뤄보겠습니다.
이것도 유한체의 독특한 접근 방식이 있더라구요!!

---

여러분의 좋아요와 공유는 사랑입니다 🥰

#BQ개발자 #ZK개발 #유한체 #Circom #영지식증명
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7
📌 ZK 개발자가 알아야 할 핵심 개념 2편: 유한체 - 음수 없이 빼기

안녕하세요, BQ개발자입니다.

지난 편에서 유한체는 오버플로우가 정상 동작이라고 했습니다.
오늘은 제가 이해하는 데 시간이 좀 걸렸던 개념을 다룹니다.

ZK 회로를 디버깅하다가 이상한 현상을 발견할 수 있습니다.
5 - 10을 계산했는데 `21888242871839275222246405745257275088696311157297823662689037894645226208578`이 나오는 거죠.
버그인 줄 알고 한참 헤멜 수 있지만, 알고 보면 정상입니다.


🤔 음수가 거대한 양수가 되는 이유

일반적으로 5 - 10 = -5죠.
그런데 유한체에는 음수가 없습니다. 0부터 p-1까지만 있어요.

그럼 5 - 10은 어떻게 표현될까요?

유한체 Fp에서는 0 아래로 가면 반대편 끝으로 돌아옵니다.
0 - 1 = p - 1이 되는 거죠.

5 - 10 = p - 5가 되고, 이게 바로 그 78자리 숫자입니다.

신기한 건 이 큰 수가 정확히 -5처럼 동작한다는 점입니다.
여기에 5를 더하면 0이 되고, 15를 더하면 10이 되거든요.


1️⃣ 토큰 전송 로직의 변화

일반 프로그래밍에서 토큰 전송을 구현할 때는 항상 잔액을 먼저 확인합니다.
잔액이 부족하면 음수가 되니까 막아야 하죠.

유한체에서는 다르게 접근한다고 합니다.

잔액 5에서 10을 빼면 그 거대한 78자리 숫자가 나옵니다.
정상적인 잔액은 보통 20자리 이하인데, 갑자기 78자리가 나온 거죠.

이 "비정상적으로 큰 값"을 걸러내는 방식으로 부정한 전송을 방지한다고 하네요.
조건문 대신 값의 크기로 판단하는 거죠.


2️⃣ 회로가 단순해지는 효과

ZK 회로에서 if-else 같은 분기문은 구현이 복잡하다고 합니다.

"조건이 참이면 A, 거짓이면 B"를 회로로 만들려면,
양쪽 경로를 다 계산한 뒤 선택하는 구조가 필요하다고 하더라고요.

유한체를 활용하면:
- 조건 확인 없이 바로 계산
- 결과값의 크기로 유효성 판단

직선적인 연산으로 처리되니 회로가 간단해진다고 합니다.


3️⃣ 제약이 오히려 편리함을 만드는 경우

처음엔 음수가 없다는 게 불편할 것 같았습니다.

그런데 생각해보니, 음수를 따로 관리하지 않아도 되는 거...?
부호 비트도 필요 없고, 양수인지 음수인지 체크하는 코드도 필요 없죠.

모든 값을 동일한 방식으로 처리할 수 있다는 점이 오히려 장점인 것 같습니다.


✏️ 유한체를 공부하면서 느낀 점 : 없는 게 있는 것

유한체 개념을 보면 음수가 "없다"고 하지만, 사실은 큰 양수로 "있는" 거라는점,
단지 표현 방식이 다를 뿐, 수학적 역할은 동일합니다.

수학이 정말 재미있는 건, 없애버린 것(음수)이 사실은 다른 형태(큰 양수)로 완벽하게 존재한다는 점인 것 같습니다.
제약을 만들었더니 오히려 더 자유로워진 거죠.

-5든 p-5든, 5를 더하면 0이 되는 건 같으니까..?

---

여러분의 좋아요와 공유는 사랑입니다 🥰

#BQ개발자 #ZK개발 #유한체 #영지식증명 #뺄셈
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3