2017 幎ã®åãã«ãç§ãã¡ã¯åŸæ¥ã® P2P ã¡ãã»ã³ãžã£ãŒã«å¯Ÿããå©ç¹ã«ã€ããŠè°è«ãããããã¯ãã§ãŒã³äžã«ã¡ãã»ã³ãžã£ãŒãäœæãå§ããŸãã [ååãšãªã³ã¯ã¯ãããã£ãŒã«ã«ãããŸã]ã
åæ ŒããŸãã 2.5
ã¡ãã»ã³ãžã£ãŒ ã¢ããªã±ãŒã·ã§ã³ã iOSãWeb PWAãWindowsãGNU/LinuxãMac OSãAndroid ã§å©çšã§ããããã«ãªããŸããããšããã³ã³ã»ããã確èªããããšãã§ããŸããã
ä»æ¥ã¯ããããã¯ãã§ãŒã³ ã¡ãã»ã³ãžã£ãŒãã©ã®ããã«æ©èœããã®ãããããŠã¯ã©ã€ã¢ã³ã ã¢ããªã±ãŒã·ã§ã³ããã® API ãã©ã®ããã«æäœã§ããã®ãã«ã€ããŠèª¬æããŸãã
ç§ãã¡ã¯ãåŸæ¥ã® P2P ã¡ãã»ã³ãžã£ãŒã®ã»ãã¥ãªãã£ãšãã©ã€ãã·ãŒã®åé¡ããããã¯ãã§ãŒã³ã§è§£æ±ºããããšèããŠããŸããã
- ã¯ã³ã¯ãªãã¯ã§ã¢ã«ãŠã³ããäœæã§ããŸããé»è©±ãã¡ãŒã«ã¯å¿ èŠãããŸãããã¢ãã¬ã¹åž³ãäœçœ®æ å ±ãžã®ã¢ã¯ã»ã¹ãå¿ èŠãããŸããã
- 察話è ã¯çŽæ¥æ¥ç¶ã確ç«ããããšã¯ãªãããã¹ãŠã®éä¿¡ã¯ããŒãã®åæ£ã·ã¹ãã ãéããŠè¡ãããŸãã ãŠãŒã¶ãŒã® IP ã¢ãã¬ã¹ã¯çžäºã«ã¢ã¯ã»ã¹ã§ããŸããã
- ãã¹ãŠã®ã¡ãã»ãŒãžã¯ãšã³ãããŒãšã³ãã§æå·åãããŸãïŒcurve25519xsalsa20poly1305ïŒã ããã¯èª°ãé©ããªããšæãããŸãããç§ãã¡ã®ãœãŒã¹ã³ãŒãã¯ãªãŒãã³ã§ãã
- MITM æ»æã¯é€å€ãããŸããåã¡ãã»ãŒãžã¯ãã©ã³ã¶ã¯ã·ã§ã³ã§ãããEd25519 EdDSA ã«ãã£ãŠçœ²åãããŠããŸãã
- ã¡ãã»ãŒãžã¯ç¬èªã®ãããã¯ã§çµäºããŸãã äžè²«æ§ãš
timestamp
ãããã¯ãä¿®æ£ããããšã¯ã§ããªããããã¡ãã»ãŒãžã®é åºãä¿®æ£ã§ããŸããã - ãç§ã¯ãããªããšã¯èšã£ãŠããŸãããã¯ãããã¯ãã§ãŒã³äžã®ã¡ãã»ãŒãžã§ã¯æ©èœããŸããã
- ã¡ãã»ãŒãžã®ãä¿¡é Œæ§ãããã§ãã¯ããäžå¿çãªæ§é ã¯ãããŸããã ããã¯åæã«åºã¥ããããŒãã®åæ£ã·ã¹ãã ã«ãã£ãŠè¡ããããŠãŒã¶ãŒãææããŸãã
- æ€é²ã®äžå¯èœæ§ - ã¢ã«ãŠã³ãããããã¯ããããã¡ãã»ãŒãžãåé€ãããããããšã¯ã§ããŸããã
- ãããã¯ãã§ãŒã³ 2FA ã¯ãSMS ã«ããå°çã®ãã㪠2FA ã®ä»£æ¿æ段ã§ãã
å€ãã®å¥åº·ã害ããŸããã - ãã€ã§ãã©ã®ããã€ã¹ããã§ããã¹ãŠã®äŒè©±ãååŸã§ãããšããããšã¯ãäŒè©±ãããŒã«ã«ã«ä¿åããå¿ èŠããŸã£ãããªãããšãæå³ããŸãã
- ã¡ãã»ãŒãžé ä¿¡ã®ç¢ºèªã ãŠãŒã¶ãŒã®ããã€ã¹ã§ã¯ãªãããããã¯ãŒã¯ã«å¯ŸããŠã§ãã åºæ¬çã«ãããã¯åä¿¡è ãã¡ãã»ãŒãžãèªãèœåãããããšã確èªãããã®ã§ãã ããã¯éèŠãªéç¥ãéä¿¡ããå Žåã«äŸ¿å©ãªæ©èœã§ãã
ãããã¯ãã§ãŒã³ã®å©ç¹ã«ã¯ãæå·é貚ã€ãŒãµãªã¢ã ãããŒãžã³ã€ã³ããªã¹ã¯ãããã·ã¥ããããã³ã€ã³ (ããã¯ãŸã éçºäž) ãšã®ç·å¯ãªçµ±åãããã£ããã§ããŒã¯ã³ãéä¿¡ã§ããæ©èœãå«ãŸããŸãã çµã¿èŸŒã¿ã®æå·é貚亀ææ©ãäœæããŸããã
ãããŠããããã©ã®ããã«æ©èœãããã
ã¡ãã»ãŒãžã¯ãã©ã³ã¶ã¯ã·ã§ã³ã§ã
ãããã¯ãã§ãŒã³å ã®ãã©ã³ã¶ã¯ã·ã§ã³ã«ãã£ãŠããããŠãŒã¶ãŒããå¥ã®ãŠãŒã¶ãŒã«ããŒã¯ã³ (ã³ã€ã³) ã転éããããšããäºå®ã«ã¯ã誰ãããã§ã«æ £ããŠããŸãã ãããã³ã€ã³ã®ããã«ã ã¡ãã»ãŒãžãéä¿¡ããããã®ç¹å¥ãªã¿ã€ãã®ãã©ã³ã¶ã¯ã·ã§ã³ãäœæããŸããã
ãããã¯ãã§ãŒã³äžã®ã¡ãã»ã³ãžã£ãŒã§ã¡ãã»ãŒãžãéä¿¡ããã«ã¯ãããã€ãã®æé ãå®è¡ããå¿ èŠããããŸãã
- ã¡ãã»ãŒãžããã¹ããæå·åãã
- æå·æããã©ã³ã¶ã¯ã·ã§ã³ã«å ¥ãã
- ãã©ã³ã¶ã¯ã·ã§ã³ã«çœ²åãã
- ãã©ã³ã¶ã¯ã·ã§ã³ãä»»æã®ãããã¯ãŒã¯ ããŒãã«éä¿¡ããŸã
- ããŒãã®åæ£ã·ã¹ãã ãã¡ãã»ãŒãžã®ãä¿¡é Œæ§ããå€æããŸã
- ãã¹ãŠOKã§ããã°ãã¡ãã»ãŒãžãå«ããã©ã³ã¶ã¯ã·ã§ã³ã次ã®ãããã¯ã«å«ãŸããŸãã
- åä¿¡è ã¯ã¡ãã»ãŒãž ãã©ã³ã¶ã¯ã·ã§ã³ãååŸãã埩å·åããŸãã
ã¹ããã 1 ïœ 3 ããã³ 7 ã¯ã¯ã©ã€ã¢ã³ãäžã§ããŒã«ã«ã«å®è¡ãããã¹ããã 5 ïœ 6 ã¯ãã¹ãäžã§å®è¡ãããŸãã
ã¡ãã»ãŒãžã®æå·å
ã¡ãã»ãŒãžã¯ãéä¿¡è
ã®ç§å¯ããŒãšåä¿¡è
ã®å
¬éããŒã䜿çšããŠæå·åãããŸãã ãããã¯ãŒã¯ããå
¬éããŒãååŸããŸããããã®ããã«ã¯åä¿¡è
ã®ã¢ã«ãŠã³ããåæåãããŠããå¿
èŠããããŸããã€ãŸããå°ãªããšã XNUMX ã€ã®ãã©ã³ã¶ã¯ã·ã§ã³ãå¿
èŠã§ãã RESTãªã¯ãšã¹ãã䜿çšã§ããŸã GET /api/accounts/getPublicKey?address={ADAMANT address}
ããã£ãããããŒããããšã察話è
ã®å
¬éããŒããã§ã«å©çšå¯èœã«ãªããŸãã
ã¡ãã»ã³ãžã£ãŒã¯ãcurve25519xsalsa20poly1305 ã¢ã«ãŽãªãºã (
JavaScript ã®äŸã次ã«ç€ºããŸãã
/**
* Encodes a text message for sending to ADM
* @param {string} msg message to encode
* @param {*} recipientPublicKey recipient's public key
* @param {*} privateKey our private key
* @returns {{message: string, nonce: string}}
*/
adamant.encodeMessage = function (msg, recipientPublicKey, privateKey) {
const nonce = Buffer.allocUnsafe(24)
sodium.randombytes(nonce)
if (typeof recipientPublicKey === 'string') {
recipientPublicKey = hexToBytes(recipientPublicKey)
}
const plainText = Buffer.from(msg)
const DHPublicKey = ed2curve.convertPublicKey(recipientPublicKey)
const DHSecretKey = ed2curve.convertSecretKey(privateKey)
const encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey)
return {
message: bytesToHex(encrypted),
nonce: bytesToHex(nonce)
}
}
ã¡ãã»ãŒãžã䜿çšããŠãã©ã³ã¶ã¯ã·ã§ã³ã圢æãã
ãã©ã³ã¶ã¯ã·ã§ã³ã«ã¯æ¬¡ã®äžè¬çãªæ§é ããããŸãã
{
"id": "15161295239237781653",
"height": 7585271,
"blockId": "16391508373936326027",
"type": 8,
"block_timestamp": 45182260,
"timestamp": 45182254,
"senderPublicKey": "bd39cc708499ae91b937083463fce5e0668c2b37e78df28f69d132fce51d49ed",
"senderId": "U16023712506749300952",
"recipientId": "U17653312780572073341",
"recipientPublicKey": "23d27f616e304ef2046a60b762683b8dabebe0d8fc26e5ecdb1d5f3d291dbe21",
"amount": 204921300000000,
"fee": 50000000,
"signature": "3c8e551f60fedb81e52835c69e8b158eb1b8b3c89a04d3df5adc0d99017ffbcb06a7b16ad76d519f80df019c930960317a67e8d18ab1e85e575c9470000cf607",
"signatures": [],
"confirmations": 3660548,
"asset": {}
}
ã¡ãã»ãŒãžãã©ã³ã¶ã¯ã·ã§ã³ã®å ŽåãæãéèŠãªããšã¯æ¬¡ã®ãšããã§ãã asset
- ãªããžã§ã¯ãã«ã¡ãã»ãŒãžãé
眮ããå¿
èŠããããŸã chat
æ§é ä»ã:
message
- æå·åãããã¡ãã»ãŒãžãä¿åããŸãown_message
-ãã³ã¹type
â ã¡ãã»ãŒãžã¿ã€ã
ã¡ãã»ãŒãžãçš®é¡ã«åãããŠããŸãã åºæ¬çã«ããã©ã¡ãŒã¿ã¯ type
ç解ããæ¹æ³ãæããŠããã message
ã ããã¹ãã ããéä¿¡ããããšããèå³æ·±ãå
容ãå«ãŸãããªããžã§ã¯ããéä¿¡ããããšãã§ããŸããããšãã°ãããã¯ã¡ãã»ã³ãžã£ãŒããã£ããã§æå·é貚ã®ééãè¡ãæ¹æ³ã§ãã
ãã®çµæããã©ã³ã¶ã¯ã·ã§ã³ãäœæãããŸãã
{
"transaction": {
"type": 8,
"amount": 0,
"senderId": "U12499126640447739963",
"senderPublicKey": "e9cafb1e7b403c4cf247c94f73ee4cada367fcc130cb3888219a0ba0633230b6",
"asset": {
"chat": {
"message": "cb682accceef92d7cddaaddb787d1184ab5428",
"own_message": "e7d8f90ddf7d70efe359c3e4ecfb5ed3802297b248eacbd6",
"type": 1
}
},
"recipientId": "U15677078342684640219",
"timestamp": 63228087,
"signature": "ÑÑÑ Ð±ÑÐŽÐµÑ Ð¿ÐŸÐŽÐ¿ÐžÑÑ"
}
}
ãã©ã³ã¶ã¯ã·ã§ã³çœ²å
éä¿¡è ãšåä¿¡è ãéä¿¡æå»ãã¡ãã»ãŒãžã®å 容ã®ä¿¡é Œæ§ã誰ãã確信ã§ããããã«ããã©ã³ã¶ã¯ã·ã§ã³ã«ã¯çœ²åãè¡ãããŸãã ããžã¿ã«çœ²åã䜿çšãããšãå ¬éããŒã䜿çšããŠãã©ã³ã¶ã¯ã·ã§ã³ã®ä¿¡é Œæ§ãæ€èšŒã§ããŸããããã«ã¯ç§å¯ããŒã¯å¿ èŠãããŸããã
ãã ãã眲åèªäœã¯ç§å¯ããŒã䜿çšããŠå®è¡ãããŸãã
ãã®å³ã¯ãæåã«ãã©ã³ã¶ã¯ã·ã§ã³ã SHA-256 ã§ããã·ã¥ãã次ã«çœ²åããããšã瀺ããŠããŸãã signature
ããã©ã³ã¶ã¯ã·ã§ã³ ID 㯠SHA-256 ããã·ã¥ã®äžéšã§ãã
å®è£ äŸ:
1 â ã¡ãã»ãŒãžãå«ãããŒã¿ ãããã¯ã圢æãã
/**
* Calls `getBytes` based on transaction type
* @see privateTypes
* @implements {ByteBuffer}
* @param {transaction} trs
* @param {boolean} skipSignature
* @param {boolean} skipSecondSignature
* @return {!Array} Contents as an ArrayBuffer.
* @throws {error} If buffer fails.
*/
adamant.getBytes = function (transaction) {
...
switch (transaction.type) {
case constants.Transactions.SEND:
break
case constants.Transactions.CHAT_MESSAGE:
assetBytes = this.chatGetBytes(transaction)
assetSize = assetBytes.length
break
âŠ
default:
alert('Not supported yet')
}
var bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true)
bb.writeByte(transaction.type)
bb.writeInt(transaction.timestamp)
...
bb.flip()
var arrayBuffer = new Uint8Array(bb.toArrayBuffer())
var buffer = []
for (var i = 0; i < arrayBuffer.length; i++) {
buffer[i] = arrayBuffer[i]
}
return Buffer.from(buffer)
}
2 - ããŒã¿ ãããã¯ãã SHA-256 ãã«ãŠã³ãããŸãã
/**
* Creates hash based on transaction bytes.
* @implements {getBytes}
* @implements {crypto.createHash}
* @param {transaction} trs
* @return {hash} sha256 crypto hash
*/
adamant.getHash = function (trs) {
return crypto.createHash('sha256').update(this.getBytes(trs)).digest()
}
3 â ãã©ã³ã¶ã¯ã·ã§ã³ã«çœ²åãã
adamant.transactionSign = function (trs, keypair) {
var hash = this.getHash(trs)
return this.sign(hash, keypair).toString('hex')
}
/**
* Creates a signature based on a hash and a keypair.
* @implements {sodium}
* @param {hash} hash
* @param {keypair} keypair
* @return {signature} signature
*/
adamant.sign = function (hash, keypair) {
return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex'))
}
ã¡ãã»ãŒãžãå«ããã©ã³ã¶ã¯ã·ã§ã³ããããã¯ãŒã¯ ããŒãã«éä¿¡ãã
ãããã¯ãŒã¯ã¯åæ£åãããŠããããããªãŒãã³ API ãåããããŒãã§ããã°ã©ãã§ã䜿çšã§ããŸãã ãšã³ããã€ã³ããžã® POST ãªã¯ãšã¹ãã®å®è¡ api/transactions
:
curl 'api/transactions' -X POST
-d 'TX_DATA'
å¿çãšããŠã次ã®ã¿ã€ãã®ãã©ã³ã¶ã¯ã·ã§ã³ ID ãåãåããŸãã
{
"success": true,
"nodeTimestamp": 63228852,
"transactionId": "6146865104403680934"
}
ãã©ã³ã¶ã¯ã·ã§ã³ã®æ€èšŒ
ããŒãã®åæ£ã·ã¹ãã ã¯ãã³ã³ã»ã³ãµã¹ã«åºã¥ããŠããã©ã³ã¶ã¯ã·ã§ã³ ã¡ãã»ãŒãžã®ãä¿¡é Œæ§ãã決å®ããŸãã 誰ãã誰ã«ããã€ãã¡ãã»ãŒãžãå·®ãæ¿ããããããéä¿¡æå»ã¯æ£ãã瀺ãããŠãããã ããã¯ãããã¯ãã§ãŒã³ã®éåžžã«éèŠãªå©ç¹ã§ããæ€èšŒãæ åœããäžå€®æ§é ãååšãããäžé£ã®ã¡ãã»ãŒãžãšãã®å 容ãåœé ããããšã¯ã§ããŸããã
ãŸããXNUMX ã€ã®ããŒãã粟床ããã§ãã¯ãããããä»ã®ããŒãã«éä¿¡ããŸãã倧å€æ°ããã¹ãŠãæ£åžžã§ãããšå€æããå Žåããã©ã³ã¶ã¯ã·ã§ã³ã¯ãã§ãŒã³ã®æ¬¡ã®ãããã¯ã«å«ãŸããŸãããããã³ã³ã»ã³ãµã¹ã§ãã
ãã§ãã¯ãæ
åœããããŒã ã³ãŒãã®éšåã¯ãGitHub ã§è¡šç€ºã§ããŸãã
ã¡ãã»ãŒãžãå«ããã©ã³ã¶ã¯ã·ã§ã³ããããã¯ã«å«ãã
åæã«éããå Žåãã¡ãã»ãŒãžãå«ããã©ã³ã¶ã¯ã·ã§ã³ã¯ä»ã®æå¹ãªãã©ã³ã¶ã¯ã·ã§ã³ãšãšãã«æ¬¡ã®ãããã¯ã«å«ãŸããŸãã
ãããã¯ã«ã¯å³å¯ãªé åºããããåŸç¶ã®åãããã¯ã¯åã®ãããã¯ã®ããã·ã¥ã«åºã¥ããŠåœ¢æãããŸãã
éèŠãªã®ã¯ããã®ã·ãŒã±ã³ã¹ã«ã¯ç§ãã¡ã®ã¡ãã»ãŒãžãå«ãŸããŠãããã䞊ã¹æ¿ãããããšã¯ã§ããªããšããããšã§ãã è€æ°ã®ã¡ãã»ãŒãžã XNUMX ã€ã®ãããã¯ã«åé¡ãããå Žåããã®é åºã¯æ¬¡ã®ããã«æ±ºå®ãããŸãã timestamp
ã¡ãã»ãŒãžã
ã¡ãã»ãŒãžãèªã
ã¡ãã»ã³ãžã£ãŒ ã¢ããªã±ãŒã·ã§ã³ã¯ãåä¿¡è
ã«éä¿¡ããããã©ã³ã¶ã¯ã·ã§ã³ããããã¯ãã§ãŒã³ããååŸããŸãã ãã®ããã«ãšã³ããã€ã³ããäœæããŸãã api/chatrooms
.
ãã¹ãŠã®ãã©ã³ã¶ã¯ã·ã§ã³ã¯èª°ã§ãå©çšã§ããæå·åãããã¡ãã»ãŒãžãåä¿¡ã§ããŸãã ãã ããç§å¯éµãšéä¿¡è ã®å ¬ééµã䜿çšããŠåŸ©å·åã§ããã®ã¯åä¿¡è ã ãã§ãã
**
* Decodes the incoming message
* @param {any} msg encoded message
* @param {string} senderPublicKey sender public key
* @param {string} privateKey our private key
* @param {any} nonce nonce
* @returns {string}
*/
adamant.decodeMessage = function (msg, senderPublicKey, privateKey, nonce) {
if (typeof msg === 'string') {
msg = hexToBytes(msg)
}
if (typeof nonce === 'string') {
nonce = hexToBytes(nonce)
}
if (typeof senderPublicKey === 'string') {
senderPublicKey = hexToBytes(senderPublicKey)
}
if (typeof privateKey === 'string') {
privateKey = hexToBytes(privateKey)
}
const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey)
const DHSecretKey = ed2curve.convertSecretKey(privateKey)
const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey)
return decrypted ? decode(decrypted) : ''
}
ä»ã«äœïŒ
ãã®æ¹æ³ã§ã¡ãã»ãŒãžã¯çŽ 5 ç§ä»¥å ã«é ä¿¡ããããã (ããã¯ãæ°ãããããã¯ãŒã¯ ãããã¯ã衚瀺ãããæéã§ã)ãã¯ã©ã€ã¢ã³ãããããŒããžãããã³ããŒââãããããŒããžã®ãœã±ããæ¥ç¶ãæãã€ããŸããã ããŒãã¯æ°ãããã©ã³ã¶ã¯ã·ã§ã³ãåä¿¡ãããšããã®æå¹æ§ããã§ãã¯ããŠä»ã®ããŒãã«è»¢éããŸãã ã¡ãã»ã³ãžã£ãŒ ã¯ã©ã€ã¢ã³ãã¯ãã³ã³ã»ã³ãµã¹ãçºçããŠãããã¯ã«å«ããããåã§ããã©ã³ã¶ã¯ã·ã§ã³ãå©çšã§ããŸãã ããã«ãããéåžžã®ã€ã³ã¹ã¿ã³ã ã¡ãã»ã³ãžã£ãŒãšåãããã«ãã¡ãã»ãŒãžãå³åº§ã«é ä¿¡ã§ããŸãã
ã¢ãã¬ã¹åž³ãä¿åããããã«ãKVS (Key-Value Storage) ãäœæããŸãããããã¯ãå¥ã®çš®é¡ã®ãã©ã³ã¶ã¯ã·ã§ã³ã§ãã asset
æå·åãããŠããã®ã¯ NaCl ããã¯ã¹ã§ã¯ãããŸãããã
ãã¡ã€ã«/ç»åã®è»¢éãšã°ã«ãŒã ãã£ããã«ã¯äŸç¶ãšããŠå€ãã®äœæ¥ãå¿ èŠã§ãã ãã¡ããã倧倱æãéãã圢åŒã§ã¯ãããã¯ããã«ãå°ç¡ããã«ãªãå¯èœæ§ããããŸãããç§ãã¡ã¯åãã¬ãã«ã®ãã©ã€ãã·ãŒãç¶æããããšèããŠããŸãã
ã¯ããããã¹ãããšã¯ãŸã ãããŸããçæ³çã«ã¯ãæ¬åœã®ãã©ã€ãã·ãŒã§ã¯ããŠãŒã¶ãŒããããªã㯠ãããã¯ãŒã¯ ããŒãã«æ¥ç¶ãããèªåã§æ¥ç¶ããããšãåæãšããŠããŸãã ãŠãŒã¶ãŒã®äœããŒã»ã³ãããããè¡ããšæããŸãã? ããã§ãã0ãTor ããŒãžã§ã³ã®ã¡ãã»ã³ãžã£ãŒã§ãã®åé¡ãéšåçã«è§£æ±ºããããšãã§ããŸããã
ç§ãã¡ã¯ããããã¯ãã§ãŒã³äžã«ã¡ãã»ã³ãžã£ãŒãååšã§ããããšã蚌æããŸããã 以åã¯ã2012 幎㫠XNUMX åã ãè©Šã¿ããããŸãã -
ãããŠæçè«ã¯ããããã¯ãã§ãŒã³äžã®ã¡ãã»ã³ãžã£ãŒãæ代ãå
åãããŠãããšããäºå®ã«ãããã®ã§ãã人ã
ã¯èªåã®ã¢ã«ãŠã³ãã«è²¬ä»»ãè² ãæºåãã§ããŠããããå人æ
å ±ãææããããšã¯ãŸã ãã¬ã³ãã§ã¯ãªããæè¡ã«ãã£ãŠãããã¯ãã§ãŒã³ã®é«éåãå¯èœã§ã¯ãããŸããã ç§ãã¡ã®ãããžã§ã¯ãã«é¡äŒŒããæè¡ãããã«ç»å Žããäºå®ã§ãã ãããã§ããã
åºæïŒ habr.com