Indy部署及开发

Indy部署及开发

1、开发环境

​ 开发基于indy-sdk,其支持的系统有 Ubuntu (1604、1804)、Windows、IOS、Android、Centos、MacOS,支持的语言和平台有Java、Python、IOS、Nodejs、.Net、Rust。

​ 这里我将使用 Ubuntu18.04 作为开发环境,使用Python进行开发测试。

​ 环境不同请参考:https://github.com/hyperledger/indy-sdk/blob/master/README.md#how-to-start-local-nodes-pool-with-docker

  • 安装Docker(略)

  • 安装libindy

    环境不同参考上面链接

    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88
    sudo add-apt-repository "deb https://repo.sovrin.org/sdk/deb bionic master"
    sudo apt-get update
    sudo apt-get install -y libindy
    
  • 安装libvcx

    环境不同请参考:https://github.com/hyperledger/indy-sdk/tree/master/vcx

    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88
    sudo add-apt-repository "deb https://repo.sovrin.org/sdk/deb bionic master"
    sudo apt-get update
    sudo apt-get install -y libvcx
    
  • 下载indy-sdk

    git clone https://github.com/hyperledger/indy-sdk.git
    
  • 安装Python依赖

    注意:应使用Python3 !

     pip3 install python3-indy asyncio
    

    可以执行下面命令检测依赖是否正常(提示连接超时为正常现象)

    # 当前目录:indy-sdk
    cd samples/python
    python3 -m src.main
    
  • 在本地主机上启动测试pool

    其他启动方式清参考上面第一个链接中的文档

    docker build -f ci/indy-pool.dockerfile -t indy_pool .
    docker run -itd -p 9701-9708:9701-9708 indy_pool
    
  • 运行Sample进行测试

    依赖于上面测试pool,正常运行则说明环境没问题

    # 当前目录:indy-sdk
    cd samples/python
    python3 -m src.main
    

    成功运行截图:


    2020-04-22 15-18-08屏幕截图.png
  • Indy-Node详细部署

    请参考:

    https://hyperledger-indy.readthedocs.io/projects/node/en/latest/start-nodes.html#scripts-for-initialization

2、开发指南(Python)

具体请参考:

https://digitid.cn/hyperledger/indy/indy-walkthrough

https://github.com/hyperledger/indy-sdk/tree/master/docs/how-tos

相关完整代码:

https://github.com/hyperledger/indy-sdk/blob/master/samples/python/src/getting_started.py

1)steward agent 连接pool

  pool_name = 'pool1'
  
  pool_genesis_txn_path = get_pool_genesis_txn_path(pool_name)
  #get_pool_genesis_txn_path在utils.py定义,创世交易
  
  pool_config = json.dumps({"genesis_txn": str(pool_genesis_txn_path)})
  
  await pool.create_pool_ledger_config(pool_name, pool_config)
  #创建一个已命名的 pool configuration
  
  pool_handle = await pool.open_pool_ledger(pool_name, None)
  #连接在这个 configuration 里提到的 nodes pool

2) steward agent 创建钱包和DID

steward agent 应该取得在账本上以 steward角色记录的拥有对应的 NYM transactions 的 DID 的 ownership。

 steward_wallet_name = 'sovrin_steward_wallet'
 await wallet.create_wallet(pool_name, steward_wallet_name, None, None, None)
 #创建一个已命名钱包
 
 steward_wallet = await wallet.open_wallet(steward_wallet_name, None, None)
#打开/连接钱包

 steward_did_info = {'seed': '000000000000000000000000Steward1'}
 (steward_did, steward_key) = await did.create_and_store_my_did(steward_wallet, json.dumps(steward_did_info))
#指定seed生成didi和verkey

3)第三方机构连接 steward agent

​ 每个连接实际上应该是一个 Pairwise-Unique Identifiers(DIDs)对(pair)。一个 DID 是由与之连接的一方所有,第二个应该是另一方所有。

​ 双方都知道两个 DIDs 并且知道这个对儿描述的是什么连接。

​ 他们之间的关系是不会共享给其他人的,对于这两方来说,每一个 pairwise 关系都会使用不同的 DIDs,并且每个 DID 都是唯一的。

​ 我们将建立连接的这个过程称为 Onboarding。

以下以faber学校与steward连接为例:

  • Steward 在它的钱包中创建了一个新的 DID,并且这个 DID 仅仅会被用于同 Faber 之间的互动。

    (steward_faber_did, steward_faber_key) = await did.create_and_store_my_did(steward_wallet, "{}")
    
  • Steward 将对应的 NYM transaction 发送到账本中,创建并发送 NYM 请求。

    nym_request = await ledger.build_nym_request(steward_did, steward_faber_did, steward_faber_key, None, role)
    await ledger.sign_and_submit_request(pool_handle, steward_wallet, steward_did, nym_request)
    
  • Steward 创建了一个包含新建的 DIDNonce 的连接请求,并把请求发送给Faber。

    connection_request = {
        'did': steward_faber_did,
        'nonce': 123456789
    }
    
  • Faber 接受请求

  • Faber 创建对应DID,这个 DID 仅仅会被用于同 Steward 进行安全的交互。

    如果Faber没有钱包会先创建钱包:

    await wallet.create_wallet(pool_name, 'faber_wallet', None, None, None)
    faber_wallet = await wallet.open_wallet('faber_wallet', None, None)
    

    创建DID:

    (faber_steward_did, faber_steward_key) = await did.create_and_store_my_did(faber_wallet, "{}")
    
  • Faber 创建了一个连接反馈(connection response),这个反馈包含了来自于接收到的连接请求的新创建的 DIDVerkeyNonce

    connection_response = json.dumps({
        'did': faber_steward_did,
        'verkey': faber_steward_key,
        'nonce': connection_request['nonce']
    })
    
  • Faber 向账本请求 Steward 的 DID 的 Verification key。

    steward_faber_verkey = await did.key_for_did(pool_handle, faber_wallet, connection_request['did'])
    
  • Faber 使用 Steward verkey 将连接的 response 进行了匿名的加密。

    anoncrypted_connection_response = await crypto.anon_crypt(steward_faber_verkey, connection_response.encode('utf-8'))
    
  • Faber 将匿名加密过的连接 response 发送给 Steward

  • Steward 匿名地将这个连接 response 进行解密。

    decrypted_connection_response = \
        (await crypto.anon_decrypt(steward_wallet, steward_faber_key, anoncrypted_connection_response)).decode("utf-8")
    
  • Steward 通过对比 Nonce 来对 Feber 进行授权。

    assert connection_request['nonce'] == decrypted_connection_response['nonce']
    
  • StewardFaber 的 DID 的 NYM transaction 发送给账本。

    nym_request = await ledger.build_nym_request(steward_did, decrypted_connection_response['did'], decrypted_connection_response['verkey'], None, role)
    await ledger.sign_and_submit_request(pool_handle, steward_wallet, steward_did, nym_request)
    
  • Faber 成功跟 Steward 建立了连接

4)第三方机构创建DID,发送给Steward进行验证

第三方机构依旧以Faber为例:

  • Faber 在它的钱包中创建了一个新的 DID。

    (faber_did, faber_key) = await did.create_and_store_my_did(faber_wallet, "{}")
    
  • Faber 准备了一个将要包含这个新创建的 DID 以及 verkey 的消息。

     # Faber Agent
     faber_did_info_json = json.dumps({
         'did': faber_did,
         'verkey': faber_key
     })
    
  • Faber 对消息进行授权及加密,这个是对 authenticated-encryption schema 的一个实现。

    ​ 被授权的加密被设计用来向指定的接收者发送一个对接收者比较特别的机密的消息。发送者可以使用接收者的公钥(verkey)和它的密钥(signing)计算出一个共享的安全密钥。接收者使用发送者的公钥(verkey)和它的密钥(signing)能够计算出完全一致的共享的安全密钥。这个共享的密钥(secret key)可以在最终解密前可以用来验证被加密过的消息并没有被篡改过。

     authcrypted_faber_did_info_json = \
           await crypto.auth_crypt(faber_wallet, faber_steward_key, steward_faber_key,  faber_did_info_json.encode('utf-8'))
    
  • Faber 将加密过的消息发送给 Steward

  • Steward 将接收到的消息进行解密。

    sender_verkey, authdecrypted_faber_did_info_json = \
    await crypto.auth_decrypt(steward_handle, steward_faber_key,  authcrypted_faber_did_info_json)
    faber_did_info = json.loads(authdecrypted_faber_did_info_json)
    
  • Steward 请求 Faber 的 DID 的确认密钥(verification key)。

    faber_verkey = await did.key_for_did(pool_handle, from_wallet, faber_did_info['did'])
    
  • Steward 通过对比消息发送者的 Verkey 和从账本中获得的 Faber 的 Verkey 来给 Faber 授权(相同的情况下)。

    assert sender_verkey == faber_verkey
    
  • Steward 会以 TRUST ANCHOR 的角色将对应的 NYM transaction 发送给账本。

    nym_request = await ledger.build_nym_request(steward_did, decrypted_faber_did_info_json['did'],
                                                 decrypted_faber_did_info_json['verkey'], None, 'TRUST_ANCHOR')
    await ledger.sign_and_submit_request(pool_handle, steward_wallet, steward_did, nym_request)
    

5)设置 Credential Schemas

​ 凭证的数据模板。指凭证架构的整体,包括元数据和JSON架构。

​ 它描述了一个特定的 Credential 可以包含的属性列表,是颁发凭证的前提。(已存在的不能被更新)

一个 Credential Schema 可以被任何的 Trust Anchor 创建并存储到账本中。

​ 例如:

{
  "id": "1",
  "name": "gvt",
  "version": "1.0",
  "ver": "1.0",
  "attrNames": ["age", "sex", "height", "name"]
}

​ 下边就是 政府(Government) 是如何创建和发布成绩单的 Credential Schema 到账本中的:

  • Trust Anchor 来创建一个 Credential Schema,这个会返回新生成的 Credential Schema

    (transcript_schema_id, transcript_schema) = \
        await anoncreds.issuer_create_schema(government_did,
                                             'Transcript',
                                             '1.2',
                                             json.dumps(['first_name', 'last_name', 'degree', 'status', 'year', 'average', 'ssn']))
    
  • Trust Anchor 创建 Schema 请求,发送新创建的请求,以此将对应的 Schema transaction 发送给账本。

    schema_request = await ledger.build_schema_request(government_did, transcript_schema)
    await ledger.sign_and_submit_request(pool_handle, government_wallet, government_did, schema_request)
    
  • 用同样的方式,政府创建并发布了 Job-Certificate Credential Schema 到账本中:

     (job_certificate_schema_id, job_certificate_schema) = \
          await anoncreds.issuer_create_schema(government_did,
                                               'Job-Certificate',
                                               '0.2',
                                               json.dumps(['first_name', 'last_name', 'salary', 'employee_status', 'experience']))
    schema_request = await ledger.build_schema_request(government_did, json.dumps(to the Ledger))
    await ledger.sign_and_submit_request(pool_handle, government_wallet, government_did, schema_request)
    
  • 到目前为止,由政府发布的 成绩单(Transcript)工作证明(Job-Certificate) Credential Schemas 已经被发布在了账本上。

6)设置 Credential Definition

​ 这引用了上面的 Credential Schemas,并宣布谁将使用该架构发行凭据(在这种情况下,我们的信任锚身份),他们计划使用哪种签名方法(“ CL” =“ Camenisch Lysyanskya” ,是indy用于零知识证明的默认方法),它们计划如何处理吊销,等等。(已存在的不能被更新)

​ 一个 Credential Definition 可以被任何 Trust Anchor 创建并保存到账本中。

​ 这里,Faber 大学创建并发布了一个有关已知的 Transacript Credential Schema 的 Credential Definition 到账本中。

  • Trust Anchor 从账本中获得指定的 Credential Schema

    get_schema_request = await ledger.build_get_schema_request(faber_did, transcript_schema_id)
    get_schema_response = await ledger.submit_request(pool_handle, get_schema_request) 
    (transcript_schema_id, transcript_schema) = await ledger.parse_get_schema_response(get_schema_response)
    
  • Trust Anchor 创建Credential Definition。对于这个 Credential Schema 的私有的 Credential Definition 部分也将会被存储在钱包中,但是是不可能直接读取出来的。

    (faber_transcript_cred_def_id, faber_transcript_cred_def_json) = \
        await anoncreds.issuer_create_and_store_credential_def(faber_wallet, faber_did, transcript_schema, 'TAG1', 'CL', '{"support_revocation": false}')
    
  • Trust Anchor 将对应的 CredDef transaction 发送到账本中。

    cred_def_request = await ledger.build_cred_def_request(faber_did, faber_transcript_cred_def_json)
    await ledger.sign_and_submit_request(pool_handle, faber_wallet, faber_did, cred_def_request)
    
  • 同样的方式,Acem 公司 为这个已知的 Job-Certificate Credential Schema 创建并发布了一个 Credential Definition 到账本中。

    # Acme Agent
      get_schema_request = await ledger.build_get_schema_request(acme_did, job_certificate_schema_id)
      get_schema_response = await ledger.submit_request(pool_handle, get_schema_request)
      (job_certificate_schema_id, job_certificate_schema) = await ledger.parse_get_schema_response(get_schema_response)
    
      (acme_job_certificate_cred_def_id, acme_job_certificate_cred_def_json) = \
          await anoncreds.issuer_create_and_store_credential_def(acme_wallet, acme_did, job_certificate_schema, 'TAG1', 'CL', '{"support_revocation": false}')
    
        cred_def_request = await ledger.build_cred_def_request(acme_did, acme_job_certificate_cred_def_json)
        await ledger.sign_and_submit_request(pool_handle, acme_wallet, acme_did, cred_def_request)
    
  • 到目前为止,我们已经有了一个由 Acem 公司 发布的关于 Job-Certificate Credential Schema 的 Credential Definition,和一个由 Faber 大学 发布的有关 Transcript Credential Schema 的 Credential Definition

7)获取Credential

​ 一个 credential 是有关一个身份的部分信息 – 一个名字,年龄,信用积分…这些信息都应该是真实的。

​ Credential 是由一个发行方提供的。

​ 这里以Alice成绩单获取为例,Alice毕业于Faber学校。

  • Faber为Alice创建了一个发行 成绩单 Credential 的一个 Credential Offer。

    # Faber Agent
     transcript_cred_offer_json = await anoncreds.issuer_create_credential_offer(faber_wallet, faber_transcript_cred_def_id)
    
  • Alice 想要查看这个 成绩单 Credential 所包含的属性。

     # Alice Agent
      get_schema_request = await ledger.build_get_schema_request(alice_faber_did, transcript_cred_offer['schema_id'])
      get_schema_response = await ledger.submit_request(pool_handle, get_schema_request)
      transcript_schema = await ledger.parse_get_schema_response(get_schema_response)
    
      print(transcript_schema['data'])
      # Transcript Schema:
      {
          'name': 'Transcript',
          'version': '1.2',
          'attr_names': ['first_name', 'last_name', 'degree', 'status', 'year', 'average', 'ssn']
      }
    
  • 创建一个 Master Secret是获取成绩单的前提。

    注意:一个 Master Secret 是一个供证明人使用的关于私有数据的 item,用来保证一个 credential 能够唯一地应用于自己。Master Secret 是一个 input,这个 input 合并了来自于多个 Credentials 的数据,用来证明这些 Credentials 有一个通用的主题(common subject)(证明者)。一个 Master Secret 应该只有证明者自己知道。

    # Alice Agent
    alice_master_secret_id = await anoncreds.prover_create_master_secret(alice_wallet, None)
    
  • Alice 还需要得到对应于在 成绩单 Credential Offer 中的 cred_def_id 的 Credential Definition。

    # Alice Agent
    get_cred_def_request = await ledger.build_get_cred_def_request(alice_faber_did,  transcript_cred_offer['cred_def_id'])
    get_cred_def_response = await ledger.submit_request(pool_handle, get_cred_def_request)
    faber_transcript_cred_def = await ledger.parse_get_cred_def_response(get_cred_def_response)
    
  • Alice 已经有了用于创建一个有关发行 Faber 大学成绩单 Credential 的请求的所有信息。

    # Alice Agent
     (transcript_cred_request_json, transcript_cred_request_metadata_json) = \
          await anoncreds.prover_create_credential_req(alice_wallet, alice_faber_did, transcript_cred_offer_json,
                                                       faber_transcript_cred_def, alice_master_secret_id)
    
  • Faber 大学 为这个 成绩单 Credential Schema 中的每个属性准备 raw 和 encoded 值。Faber 大学为 Alice 创建了 成绩单 Credential。

    # Faber Agent
    # note that encoding is not standardized by Indy except that 32-bit integers are encoded as themselves. IS-786
    transcript_cred_values = json.dumps({
          "first_name": {"raw": "Alice", "encoded": "1139481716457488690172217916278103335"},
          "last_name": {"raw": "Garcia", "encoded": "5321642780241790123587902456789123452"},
          "degree": {"raw": "Bachelor of Science, Marketing", "encoded": "12434523576212321"},
          "status": {"raw": "graduated", "encoded": "2213454313412354"},
          "ssn": {"raw": "123-45-6789", "encoded": "3124141231422543541"},
          "year": {"raw": "2015", "encoded": "2015"},
          "average": {"raw": "5", "encoded": "5"}
      })
    
    transcript_cred_json, _, _ = \
          await anoncreds.issuer_create_credential(faber_wallet, transcript_cred_offer_json, transcript_cred_request_json,
                                                   transcript_cred_values, None, None)
    
  • 现在,成绩单 Credential 已经被发行出来了。Alice 将它保存在了自己的钱包中。

     # Alice Agent
    await anoncreds.prover_store_credential(alice_wallet, None, transcript_cred_request_json, transcript_cred_request_metadata_json,
                                              transcript_cred_json, faber_transcript_cred_def, None)
    

8)为其他第三方机构提供证明

这里以Alice申请工作为例,Alice需要为Acme Corp提供**工作申请表(Job Application)**,这个申请表中要求一个名字、学历、状态、SSN 和是否满足在校平均分数的一些条件。

Job-Application Proof Request如下:(属性部分是不可证实的)

 # Acme Agent
  job_application_proof_request_json = json.dumps({
      'nonce': '1432422343242122312411212',
      'name': 'Job-Application',
      'version': '0.1',
      'requested_attributes': {
          'attr1_referent': {
              'name': 'first_name'
          },
          'attr2_referent': {
              'name': 'last_name'
          },
          'attr3_referent': {
              'name': 'degree',
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          },
          'attr4_referent': {
              'name': 'status',
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          },
          'attr5_referent': {
              'name': 'ssn',
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          },
          'attr6_referent': {
              'name': 'phone_number'
          }
      },
      'requested_predicates': {
          'predicate1_referent': {
              'name': 'average',
              'p_type': '>=',
              'p_value': 4,
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          }
      }
  })
  • 显示 Alice 可以用来创建 Job-Application Proof 请求的 Proof 的 Credentials

     # Alice Agent
        creds_for_job_application_proof_request = json.loads(
            await anoncreds.prover_get_credentials_for_proof_req(alice_wallet, job_application_proof_request_json))
    

    Alice 只有一项 credential 满足 Job Application 的 proof 要求。

      # Alice Agent
      {
        'referent': 'Transcript Credential Referent',
        'attrs': {
            'first_name': 'Alice',
            'last_name': 'Garcia',
            'status': 'graduated',
            'degree': 'Bachelor of Science, Marketing',
            'ssn': '123-45-6789',
            'year': '2015',
            'average': '5'
        },
        'schema_id': job_certificate_schema_id,
        'cred_def_id': faber_transcript_cred_def_id,
        'rev_reg_id': None,
        'cred_rev_id': None
      }
    

    现在 Alice 可以将这些属性分为三个组:

    1. 属性值将会被透漏的
    2. 属性值将不会被透漏的
    3. 对于创建可证实的 proof 不需要的属性

    对于这个 Job-Application Proof Request,Alice 将属性按照下边的方式分组:

     # Alice Agent
        job_application_requested_creds_json = json.dumps({
            'self_attested_attributes': {
                'attr1_referent': 'Alice',
                'attr2_referent': 'Garcia',
                'attr6_referent': '123-45-6789'
            },
            'requested_attributes': {
                'attr3_referent': {'cred_id': cred_for_attr3['referent'], 'revealed': True},
                'attr4_referent': {'cred_id': cred_for_attr4['referent'], 'revealed': True},
                'attr5_referent': {'cred_id': cred_for_attr5['referent'], 'revealed': True},
            },
            'requested_predicates': {'predicate1_referent': {'cred_id': cred_for_predicate1['referent']}}
        })
    
  • Alice 必须为每一个使用的 Credential 取得 Credential Schema 和对应的 Credential Definition,就像在创建 Credential 请求时候的步骤一样。

  • 现在 Alice 有了创建 Acme Job-Application 创建 Proof Request 的 Proof 的所有信息。

    # Alice Agent
    apply_job_proof_json = \
            await anoncreds.prover_create_proof(alice_wallet, job_application_proof_request_json, job_application_requested_creds_json,
                                                alice_master_secret_id, schemas_json, cred_defs_json, revoc_states_json)
    
  • Acme 公司 收到了这个 Proof 的时候,他们看到的应该是下边的结构:

      # Acme Agent
      {
          'requested_proof': {
              'revealed_attrs': {
                  'attr4_referent': {'sub_proof_index': 0, 'raw':'graduated', 'encoded':'2213454313412354'},
                  'attr5_referent': ['sub_proof_index': 0, 'raw':'123-45-6789', 'encoded':'3124141231422543541'},
                  'attr3_referent': ['sub_proof_index': 0, 'raw':'Bachelor of Science, Marketing', 'encoded':'12434523576212321'}
              },
              'self_attested_attrs': {
                  'attr1_referent': 'Alice',
                  'attr2_referent': 'Garcia',
                  'attr6_referent': '123-45-6789'
              },
              'unrevealed_attrs': {},
              'predicates': {
                  'predicate1_referent': {'sub_proof_index': 0}
              }
          },
          'proof' : [] # Validity Proof that Acme can check
          'identifiers' : [ # Identifiers of credentials were used for Proof building
              {
                'schema_id': job_certificate_schema_id,
                'cred_def_id': faber_transcript_cred_def_id,
                'rev_reg_id': None,
                'timestamp': None
              }
          }
      }
    
  • Acem 得到了所有所需的属性。现在 Acem 想要校验这个 Validity Proof。

    Acem 必须要获得在 Proof 中提及的每个 identifier 的 Credential Schema 和相对应的 Credential Definition,就像 Alice 之前做的一样。现在 Acme 有了验证来自于 Alice 的 Job-Application Proof 的所有信息。

    # Acme Agent
    assert await anoncreds.verifier_verify_proof(job_application_proof_request_json, apply_job_proof_json,
                                                  schemas_json, cred_defs_json, revoc_ref_defs_json, revoc_regs_json)
    
  • 假设这个申请表被接受了并且 Alice 最终得到了这份工作。Acem 为 Alice 创建了一个新的 Credential Offer。

      # Acme Agent
     job_certificate_cred_offer_json = await anoncreds.issuer_create_credential_offer(acme_wallet, acme_job_certificate_cred_def_id)
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351